@mandujs/mcp 0.9.19 → 0.9.21
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/README.md +320 -0
- package/package.json +1 -1
- package/src/activity-monitor.ts +847 -231
- package/src/resources/handlers.ts +244 -0
- package/src/resources/skills/guides.ts +1136 -0
- package/src/resources/skills/index.ts +12 -0
- package/src/resources/skills/loader.ts +218 -0
- package/src/resources/skills/mandu-composition/SKILL.md +91 -0
- package/src/resources/skills/mandu-composition/metadata.json +13 -0
- package/src/resources/skills/mandu-composition/rules/_sections.md +26 -0
- package/src/resources/skills/mandu-composition/rules/_template.md +77 -0
- package/src/resources/skills/mandu-composition/rules/comp-arch-avoid-boolean-props.md +146 -0
- package/src/resources/skills/mandu-composition/rules/comp-arch-compound-components.md +164 -0
- package/src/resources/skills/mandu-composition/rules/comp-island-event.md +161 -0
- package/src/resources/skills/mandu-composition/rules/comp-island-slot-split.md +167 -0
- package/src/resources/skills/mandu-composition/rules/comp-pattern-children.md +149 -0
- package/src/resources/skills/mandu-composition/rules/comp-state-context-interface.md +148 -0
- package/src/resources/skills/mandu-composition/rules/comp-state-lift-state.md +150 -0
- package/src/resources/skills/mandu-deployment/SKILL.md +92 -0
- package/src/resources/skills/mandu-deployment/_sections.md +41 -0
- package/src/resources/skills/mandu-deployment/_template.md +38 -0
- package/src/resources/skills/mandu-deployment/metadata.json +13 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-build-bun.md +109 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-build-output.md +115 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-cicd-github.md +219 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-bun.md +150 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-compose.md +223 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-fly.md +152 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-render.md +179 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-supabase.md +323 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-vercel.md +140 -0
- package/src/resources/skills/mandu-fs-routes/SKILL.md +82 -0
- package/src/resources/skills/mandu-fs-routes/metadata.json +12 -0
- package/src/resources/skills/mandu-fs-routes/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-fs-routes/rules/_template.md +69 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-api-methods.md +65 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-dynamic-param.md +93 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-naming-page.md +55 -0
- package/src/resources/skills/mandu-guard/SKILL.md +129 -0
- package/src/resources/skills/mandu-guard/metadata.json +12 -0
- package/src/resources/skills/mandu-guard/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-guard/rules/_template.md +82 -0
- package/src/resources/skills/mandu-guard/rules/guard-config-rules.md +100 -0
- package/src/resources/skills/mandu-guard/rules/guard-layer-direction.md +76 -0
- package/src/resources/skills/mandu-guard/rules/guard-preset-mandu.md +81 -0
- package/src/resources/skills/mandu-guard/rules/guard-validate-import.md +80 -0
- package/src/resources/skills/mandu-hydration/SKILL.md +91 -0
- package/src/resources/skills/mandu-hydration/metadata.json +12 -0
- package/src/resources/skills/mandu-hydration/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-hydration/rules/_template.md +72 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-data-event.md +109 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-directive-use-client.md +55 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +113 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +68 -0
- package/src/resources/skills/mandu-performance/SKILL.md +85 -0
- package/src/resources/skills/mandu-performance/metadata.json +14 -0
- package/src/resources/skills/mandu-performance/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-performance/rules/_template.md +64 -0
- package/src/resources/skills/mandu-performance/rules/perf-async-defer-await.md +103 -0
- package/src/resources/skills/mandu-performance/rules/perf-async-parallel.md +95 -0
- package/src/resources/skills/mandu-performance/rules/perf-bun-file.md +124 -0
- package/src/resources/skills/mandu-performance/rules/perf-bun-serve.md +125 -0
- package/src/resources/skills/mandu-performance/rules/perf-bundle-imports.md +80 -0
- package/src/resources/skills/mandu-performance/rules/perf-bundle-island-lazy.md +145 -0
- package/src/resources/skills/mandu-performance/rules/perf-cache-react.md +98 -0
- package/src/resources/skills/mandu-performance/rules/perf-render-transitions.md +154 -0
- package/src/resources/skills/mandu-security/SKILL.md +87 -0
- package/src/resources/skills/mandu-security/metadata.json +13 -0
- package/src/resources/skills/mandu-security/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-security/rules/_template.md +74 -0
- package/src/resources/skills/mandu-security/rules/sec-auth-guard.md +127 -0
- package/src/resources/skills/mandu-security/rules/sec-env-management.md +133 -0
- package/src/resources/skills/mandu-security/rules/sec-input-validate.md +148 -0
- package/src/resources/skills/mandu-security/rules/sec-protect-csrf.md +146 -0
- package/src/resources/skills/mandu-security/rules/sec-protect-headers.md +138 -0
- package/src/resources/skills/mandu-slot/SKILL.md +85 -0
- package/src/resources/skills/mandu-slot/metadata.json +12 -0
- package/src/resources/skills/mandu-slot/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-slot/rules/_template.md +63 -0
- package/src/resources/skills/mandu-slot/rules/slot-basic-structure.md +38 -0
- package/src/resources/skills/mandu-slot/rules/slot-ctx-response.md +56 -0
- package/src/resources/skills/mandu-slot/rules/slot-guard-auth.md +59 -0
- package/src/resources/skills/mandu-slot/rules/slot-http-methods.md +64 -0
- package/src/resources/skills/mandu-styling/SKILL.md +118 -0
- package/src/resources/skills/mandu-styling/_sections.md +36 -0
- package/src/resources/skills/mandu-styling/_template.md +32 -0
- package/src/resources/skills/mandu-styling/metadata.json +13 -0
- package/src/resources/skills/mandu-styling/rules/style-component-compound.md +235 -0
- package/src/resources/skills/mandu-styling/rules/style-component-slots.md +255 -0
- package/src/resources/skills/mandu-styling/rules/style-component-tokens.md +205 -0
- package/src/resources/skills/mandu-styling/rules/style-island-animations.md +272 -0
- package/src/resources/skills/mandu-styling/rules/style-island-scoping.md +167 -0
- package/src/resources/skills/mandu-styling/rules/style-island-variants.md +221 -0
- package/src/resources/skills/mandu-styling/rules/style-perf-critical.md +209 -0
- package/src/resources/skills/mandu-styling/rules/style-perf-purge.md +192 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-modules.md +162 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-panda.md +164 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-tailwind.md +161 -0
- package/src/resources/skills/mandu-styling/rules/style-theme-darkmode.md +229 -0
- package/src/resources/skills/mandu-testing/SKILL.md +99 -0
- package/src/resources/skills/mandu-testing/metadata.json +13 -0
- package/src/resources/skills/mandu-testing/rules/_sections.md +26 -0
- package/src/resources/skills/mandu-testing/rules/_template.md +65 -0
- package/src/resources/skills/mandu-testing/rules/test-component-island.md +195 -0
- package/src/resources/skills/mandu-testing/rules/test-e2e-playwright.md +196 -0
- package/src/resources/skills/mandu-testing/rules/test-mock-fetch.md +219 -0
- package/src/resources/skills/mandu-testing/rules/test-slot-unit.md +192 -0
- package/src/resources/skills/mandu-ui/SKILL.md +117 -0
- package/src/resources/skills/mandu-ui/_sections.md +23 -0
- package/src/resources/skills/mandu-ui/_template.md +32 -0
- package/src/resources/skills/mandu-ui/metadata.json +13 -0
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-aria.md +232 -0
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-focus.md +238 -0
- package/src/resources/skills/mandu-ui/rules/ui-composition-patterns.md +259 -0
- package/src/resources/skills/mandu-ui/rules/ui-island-integration.md +258 -0
- package/src/resources/skills/mandu-ui/rules/ui-radix-patterns.md +213 -0
- package/src/resources/skills/mandu-ui/rules/ui-shadcn-setup.md +209 -0
- package/src/resources/skills/recipes.ts +932 -0
- package/src/server.ts +3 -0
- package/src/tools/hydration.ts +8 -8
- package/src/tools/index.ts +1 -0
- package/src/tools/seo.ts +417 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deploy to Fly.io
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Edge deployment for global performance
|
|
5
|
+
tags: deployment, fly, edge, platform
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Deploy to Fly.io
|
|
9
|
+
|
|
10
|
+
**Impact: MEDIUM (Edge deployment for global performance)**
|
|
11
|
+
|
|
12
|
+
Fly.io를 사용하여 전 세계 엣지에서 Mandu 앱을 실행하세요.
|
|
13
|
+
|
|
14
|
+
**fly.toml 설정:**
|
|
15
|
+
|
|
16
|
+
```toml
|
|
17
|
+
app = "mandu-app"
|
|
18
|
+
primary_region = "nrt" # Tokyo
|
|
19
|
+
|
|
20
|
+
[build]
|
|
21
|
+
dockerfile = "Dockerfile"
|
|
22
|
+
|
|
23
|
+
[env]
|
|
24
|
+
NODE_ENV = "production"
|
|
25
|
+
BUN_ENV = "production"
|
|
26
|
+
|
|
27
|
+
[http_service]
|
|
28
|
+
internal_port = 3000
|
|
29
|
+
force_https = true
|
|
30
|
+
auto_stop_machines = true
|
|
31
|
+
auto_start_machines = true
|
|
32
|
+
min_machines_running = 1
|
|
33
|
+
|
|
34
|
+
[http_service.concurrency]
|
|
35
|
+
type = "requests"
|
|
36
|
+
hard_limit = 250
|
|
37
|
+
soft_limit = 200
|
|
38
|
+
|
|
39
|
+
[[vm]]
|
|
40
|
+
cpu_kind = "shared"
|
|
41
|
+
cpus = 1
|
|
42
|
+
memory_mb = 512
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Dockerfile:**
|
|
46
|
+
|
|
47
|
+
```dockerfile
|
|
48
|
+
FROM oven/bun:1.0-slim as builder
|
|
49
|
+
|
|
50
|
+
WORKDIR /app
|
|
51
|
+
COPY package.json bun.lockb ./
|
|
52
|
+
RUN bun install --frozen-lockfile
|
|
53
|
+
|
|
54
|
+
COPY . .
|
|
55
|
+
RUN bun run build
|
|
56
|
+
|
|
57
|
+
# Production image
|
|
58
|
+
FROM oven/bun:1.0-slim
|
|
59
|
+
|
|
60
|
+
WORKDIR /app
|
|
61
|
+
COPY --from=builder /app/dist ./dist
|
|
62
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
63
|
+
COPY package.json ./
|
|
64
|
+
|
|
65
|
+
EXPOSE 3000
|
|
66
|
+
USER bun
|
|
67
|
+
|
|
68
|
+
CMD ["bun", "run", "start"]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 배포 명령어
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Fly CLI 설치
|
|
75
|
+
curl -L https://fly.io/install.sh | sh
|
|
76
|
+
|
|
77
|
+
# 로그인
|
|
78
|
+
fly auth login
|
|
79
|
+
|
|
80
|
+
# 앱 생성
|
|
81
|
+
fly launch --name mandu-app --region nrt
|
|
82
|
+
|
|
83
|
+
# 배포
|
|
84
|
+
fly deploy
|
|
85
|
+
|
|
86
|
+
# 스케일링
|
|
87
|
+
fly scale count 2 --region nrt,sin
|
|
88
|
+
|
|
89
|
+
# 로그 확인
|
|
90
|
+
fly logs
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 멀티 리전 배포
|
|
94
|
+
|
|
95
|
+
```toml
|
|
96
|
+
# fly.toml
|
|
97
|
+
app = "mandu-app"
|
|
98
|
+
primary_region = "nrt"
|
|
99
|
+
|
|
100
|
+
# 여러 리전에 배포
|
|
101
|
+
[processes]
|
|
102
|
+
app = "bun run start"
|
|
103
|
+
|
|
104
|
+
[[vm]]
|
|
105
|
+
cpu_kind = "shared"
|
|
106
|
+
cpus = 1
|
|
107
|
+
memory_mb = 512
|
|
108
|
+
|
|
109
|
+
# 리전별 스케일링
|
|
110
|
+
# fly scale count app=2 --region nrt
|
|
111
|
+
# fly scale count app=1 --region sin
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Secrets 관리
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Secret 설정
|
|
118
|
+
fly secrets set DATABASE_URL="postgresql://..."
|
|
119
|
+
fly secrets set SESSION_SECRET="..."
|
|
120
|
+
|
|
121
|
+
# Secret 목록
|
|
122
|
+
fly secrets list
|
|
123
|
+
|
|
124
|
+
# Secret 삭제
|
|
125
|
+
fly secrets unset OLD_SECRET
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 볼륨 (Persistent Storage)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# 볼륨 생성
|
|
132
|
+
fly volumes create mandu_data --region nrt --size 1
|
|
133
|
+
|
|
134
|
+
# fly.toml에 마운트
|
|
135
|
+
[mounts]
|
|
136
|
+
source = "mandu_data"
|
|
137
|
+
destination = "/data"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 헬스체크
|
|
141
|
+
|
|
142
|
+
```toml
|
|
143
|
+
# fly.toml
|
|
144
|
+
[[services.http_checks]]
|
|
145
|
+
interval = "10s"
|
|
146
|
+
timeout = "2s"
|
|
147
|
+
grace_period = "5s"
|
|
148
|
+
method = "GET"
|
|
149
|
+
path = "/health"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Reference: [Fly.io Documentation](https://fly.io/docs/)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deploy to Render
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Managed platform for production workloads
|
|
5
|
+
tags: deployment, render, platform, hosting
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Deploy to Render
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Managed platform for production workloads)**
|
|
11
|
+
|
|
12
|
+
Render를 사용하여 Mandu 앱을 배포하세요. 자동 HTTPS, 스케일링, 무중단 배포를 지원합니다.
|
|
13
|
+
|
|
14
|
+
**render.yaml (Blueprint):**
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
services:
|
|
18
|
+
- type: web
|
|
19
|
+
name: mandu-app
|
|
20
|
+
runtime: node
|
|
21
|
+
region: singapore # 또는 oregon, frankfurt 등
|
|
22
|
+
plan: starter # starter, standard, pro
|
|
23
|
+
|
|
24
|
+
buildCommand: |
|
|
25
|
+
curl -fsSL https://bun.sh/install | bash
|
|
26
|
+
export PATH="$HOME/.bun/bin:$PATH"
|
|
27
|
+
bun install --frozen-lockfile
|
|
28
|
+
bun run build
|
|
29
|
+
|
|
30
|
+
startCommand: |
|
|
31
|
+
export PATH="$HOME/.bun/bin:$PATH"
|
|
32
|
+
bun run start
|
|
33
|
+
|
|
34
|
+
healthCheckPath: /health
|
|
35
|
+
|
|
36
|
+
envVars:
|
|
37
|
+
- key: NODE_ENV
|
|
38
|
+
value: production
|
|
39
|
+
- key: BUN_ENV
|
|
40
|
+
value: production
|
|
41
|
+
- key: DATABASE_URL
|
|
42
|
+
fromDatabase:
|
|
43
|
+
name: mandu-db
|
|
44
|
+
property: connectionString
|
|
45
|
+
- key: SESSION_SECRET
|
|
46
|
+
generateValue: true
|
|
47
|
+
|
|
48
|
+
databases:
|
|
49
|
+
- name: mandu-db
|
|
50
|
+
plan: starter
|
|
51
|
+
databaseName: mandu
|
|
52
|
+
user: mandu
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 헬스체크 엔드포인트
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// app/health/slot.ts
|
|
59
|
+
import { Mandu } from "@mandujs/core";
|
|
60
|
+
|
|
61
|
+
export default Mandu.filling({
|
|
62
|
+
get: async (ctx) => {
|
|
63
|
+
// 데이터베이스 연결 확인
|
|
64
|
+
try {
|
|
65
|
+
await db.query("SELECT 1");
|
|
66
|
+
return ctx.ok({ status: "healthy", timestamp: new Date().toISOString() });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return ctx.fail("Database connection failed");
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Render Native Runtime (Bun 직접 지원)
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
# render.yaml - Bun 네이티브 (Beta)
|
|
78
|
+
services:
|
|
79
|
+
- type: web
|
|
80
|
+
name: mandu-app
|
|
81
|
+
runtime: docker
|
|
82
|
+
dockerfilePath: ./Dockerfile
|
|
83
|
+
|
|
84
|
+
envVars:
|
|
85
|
+
- key: NODE_ENV
|
|
86
|
+
value: production
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```dockerfile
|
|
90
|
+
# Dockerfile
|
|
91
|
+
FROM oven/bun:1.0-slim
|
|
92
|
+
|
|
93
|
+
WORKDIR /app
|
|
94
|
+
|
|
95
|
+
COPY package.json bun.lockb ./
|
|
96
|
+
RUN bun install --frozen-lockfile --production
|
|
97
|
+
|
|
98
|
+
COPY . .
|
|
99
|
+
RUN bun run build
|
|
100
|
+
|
|
101
|
+
EXPOSE 3000
|
|
102
|
+
USER bun
|
|
103
|
+
|
|
104
|
+
CMD ["bun", "run", "start"]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 환경별 배포
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
# render.yaml - Preview Environments
|
|
111
|
+
previewsEnabled: true
|
|
112
|
+
previewsExpireAfterDays: 7
|
|
113
|
+
|
|
114
|
+
services:
|
|
115
|
+
- type: web
|
|
116
|
+
name: mandu-app
|
|
117
|
+
# ... 기본 설정
|
|
118
|
+
|
|
119
|
+
envVars:
|
|
120
|
+
- key: NODE_ENV
|
|
121
|
+
value: production
|
|
122
|
+
- key: API_URL
|
|
123
|
+
fromGroup: mandu-production # Production
|
|
124
|
+
|
|
125
|
+
previews:
|
|
126
|
+
generation: automatic
|
|
127
|
+
envVars:
|
|
128
|
+
- key: API_URL
|
|
129
|
+
fromGroup: mandu-staging # Preview는 staging 사용
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 자동 배포 설정
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
# GitHub 연동 시 자동 배포
|
|
136
|
+
# render.yaml
|
|
137
|
+
services:
|
|
138
|
+
- type: web
|
|
139
|
+
name: mandu-app
|
|
140
|
+
repo: https://github.com/your-org/mandu-app
|
|
141
|
+
branch: main # main 브랜치 자동 배포
|
|
142
|
+
autoDeploy: true # push 시 자동 배포
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 스케일링 설정
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
services:
|
|
149
|
+
- type: web
|
|
150
|
+
name: mandu-app
|
|
151
|
+
plan: standard
|
|
152
|
+
|
|
153
|
+
scaling:
|
|
154
|
+
minInstances: 1
|
|
155
|
+
maxInstances: 5
|
|
156
|
+
targetMemoryPercent: 80
|
|
157
|
+
targetCPUPercent: 70
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Render CLI
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# CLI 설치
|
|
164
|
+
npm install -g @render/cli
|
|
165
|
+
|
|
166
|
+
# 로그인
|
|
167
|
+
render login
|
|
168
|
+
|
|
169
|
+
# 서비스 목록
|
|
170
|
+
render services list
|
|
171
|
+
|
|
172
|
+
# 수동 배포
|
|
173
|
+
render deploys create --service-id srv-xxx
|
|
174
|
+
|
|
175
|
+
# 로그 확인
|
|
176
|
+
render logs --service-id srv-xxx --tail
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Reference: [Render Documentation](https://render.com/docs)
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deploy with Supabase
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Backend-as-a-Service with PostgreSQL and Edge Functions
|
|
5
|
+
tags: deployment, supabase, database, edge, baas
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Deploy with Supabase
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Backend-as-a-Service with PostgreSQL and Edge Functions)**
|
|
11
|
+
|
|
12
|
+
Supabase를 사용하여 PostgreSQL 데이터베이스, 인증, Edge Functions를 통합하세요.
|
|
13
|
+
|
|
14
|
+
**프로젝트 설정:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Supabase CLI 설치
|
|
18
|
+
npm install -g supabase
|
|
19
|
+
|
|
20
|
+
# 로그인
|
|
21
|
+
supabase login
|
|
22
|
+
|
|
23
|
+
# 프로젝트 초기화
|
|
24
|
+
supabase init
|
|
25
|
+
|
|
26
|
+
# 로컬 개발 환경 시작
|
|
27
|
+
supabase start
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Supabase 클라이언트 설정
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// lib/supabase.ts
|
|
34
|
+
import { createClient } from "@supabase/supabase-js";
|
|
35
|
+
import type { Database } from "./database.types";
|
|
36
|
+
|
|
37
|
+
const supabaseUrl = process.env.SUPABASE_URL!;
|
|
38
|
+
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY!;
|
|
39
|
+
|
|
40
|
+
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey);
|
|
41
|
+
|
|
42
|
+
// 서버 사이드 (Service Role)
|
|
43
|
+
export function createServerClient() {
|
|
44
|
+
return createClient<Database>(
|
|
45
|
+
supabaseUrl,
|
|
46
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
|
47
|
+
{
|
|
48
|
+
auth: {
|
|
49
|
+
autoRefreshToken: false,
|
|
50
|
+
persistSession: false,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 데이터베이스 마이그레이션
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# 마이그레이션 생성
|
|
61
|
+
supabase migration new create_users_table
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```sql
|
|
65
|
+
-- supabase/migrations/20240101000000_create_users_table.sql
|
|
66
|
+
CREATE TABLE users (
|
|
67
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
68
|
+
email TEXT UNIQUE NOT NULL,
|
|
69
|
+
name TEXT,
|
|
70
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
-- RLS 활성화
|
|
74
|
+
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
75
|
+
|
|
76
|
+
-- 정책 설정
|
|
77
|
+
CREATE POLICY "Users can read own data"
|
|
78
|
+
ON users FOR SELECT
|
|
79
|
+
USING (auth.uid() = id);
|
|
80
|
+
|
|
81
|
+
CREATE POLICY "Users can update own data"
|
|
82
|
+
ON users FOR UPDATE
|
|
83
|
+
USING (auth.uid() = id);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# 마이그레이션 적용
|
|
88
|
+
supabase db push
|
|
89
|
+
|
|
90
|
+
# 타입 생성
|
|
91
|
+
supabase gen types typescript --local > lib/database.types.ts
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Mandu Slot에서 Supabase 사용
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// app/users/slot.ts
|
|
98
|
+
import { Mandu } from "@mandujs/core";
|
|
99
|
+
import { createServerClient } from "@/lib/supabase";
|
|
100
|
+
|
|
101
|
+
export default Mandu.filling({
|
|
102
|
+
get: async (ctx) => {
|
|
103
|
+
const supabase = createServerClient();
|
|
104
|
+
const user = ctx.get<User>("user");
|
|
105
|
+
|
|
106
|
+
const { data, error } = await supabase
|
|
107
|
+
.from("users")
|
|
108
|
+
.select("*")
|
|
109
|
+
.eq("id", user.id)
|
|
110
|
+
.single();
|
|
111
|
+
|
|
112
|
+
if (error) {
|
|
113
|
+
return ctx.error({ message: error.message });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return ctx.ok({ user: data });
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
post: async (ctx) => {
|
|
120
|
+
const supabase = createServerClient();
|
|
121
|
+
const body = await ctx.body<{ email: string; name: string }>();
|
|
122
|
+
|
|
123
|
+
const { data, error } = await supabase
|
|
124
|
+
.from("users")
|
|
125
|
+
.insert(body)
|
|
126
|
+
.select()
|
|
127
|
+
.single();
|
|
128
|
+
|
|
129
|
+
if (error) {
|
|
130
|
+
return ctx.error({ message: error.message });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return ctx.created({ user: data });
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Supabase Auth 통합
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// app/auth/slot.ts
|
|
142
|
+
import { Mandu } from "@mandujs/core";
|
|
143
|
+
import { supabase } from "@/lib/supabase";
|
|
144
|
+
|
|
145
|
+
export default Mandu.filling({
|
|
146
|
+
// 로그인
|
|
147
|
+
post: async (ctx) => {
|
|
148
|
+
const { email, password } = await ctx.body<{
|
|
149
|
+
email: string;
|
|
150
|
+
password: string;
|
|
151
|
+
}>();
|
|
152
|
+
|
|
153
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
154
|
+
email,
|
|
155
|
+
password,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (error) {
|
|
159
|
+
return ctx.unauthorized(error.message);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return ctx.ok({
|
|
163
|
+
user: data.user,
|
|
164
|
+
session: data.session,
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// 로그아웃
|
|
169
|
+
delete: async (ctx) => {
|
|
170
|
+
await supabase.auth.signOut();
|
|
171
|
+
return ctx.noContent();
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Auth Middleware:**
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// middleware/auth.ts
|
|
180
|
+
import { createServerClient } from "@/lib/supabase";
|
|
181
|
+
|
|
182
|
+
export async function authMiddleware(ctx: Context) {
|
|
183
|
+
const authHeader = ctx.headers.get("authorization");
|
|
184
|
+
|
|
185
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
186
|
+
return ctx.unauthorized("Missing token");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const token = authHeader.slice(7);
|
|
190
|
+
const supabase = createServerClient();
|
|
191
|
+
|
|
192
|
+
const { data: { user }, error } = await supabase.auth.getUser(token);
|
|
193
|
+
|
|
194
|
+
if (error || !user) {
|
|
195
|
+
return ctx.unauthorized("Invalid token");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
ctx.set("user", user);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Edge Functions
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Edge Function 생성
|
|
206
|
+
supabase functions new hello
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// supabase/functions/hello/index.ts
|
|
211
|
+
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
|
212
|
+
|
|
213
|
+
serve(async (req) => {
|
|
214
|
+
const { name } = await req.json();
|
|
215
|
+
|
|
216
|
+
return new Response(
|
|
217
|
+
JSON.stringify({ message: `Hello ${name}!` }),
|
|
218
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
# 로컬 테스트
|
|
225
|
+
supabase functions serve hello
|
|
226
|
+
|
|
227
|
+
# 배포
|
|
228
|
+
supabase functions deploy hello
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 환경 변수 설정
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# .env.local
|
|
235
|
+
SUPABASE_URL=http://localhost:54321
|
|
236
|
+
SUPABASE_ANON_KEY=eyJ...
|
|
237
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJ...
|
|
238
|
+
|
|
239
|
+
# 프로덕션 (Supabase Dashboard에서 확인)
|
|
240
|
+
SUPABASE_URL=https://xxx.supabase.co
|
|
241
|
+
SUPABASE_ANON_KEY=eyJ...
|
|
242
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJ...
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Supabase + Render 배포
|
|
246
|
+
|
|
247
|
+
```yaml
|
|
248
|
+
# render.yaml
|
|
249
|
+
services:
|
|
250
|
+
- type: web
|
|
251
|
+
name: mandu-app
|
|
252
|
+
runtime: node
|
|
253
|
+
buildCommand: bun install && bun run build
|
|
254
|
+
startCommand: bun run start
|
|
255
|
+
envVars:
|
|
256
|
+
- key: SUPABASE_URL
|
|
257
|
+
sync: false # Dashboard에서 설정
|
|
258
|
+
- key: SUPABASE_ANON_KEY
|
|
259
|
+
sync: false
|
|
260
|
+
- key: SUPABASE_SERVICE_ROLE_KEY
|
|
261
|
+
sync: false
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Realtime 구독 (Island)
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// app/messages/client.tsx
|
|
268
|
+
"use client";
|
|
269
|
+
|
|
270
|
+
import { useEffect, useState } from "react";
|
|
271
|
+
import { supabase } from "@/lib/supabase";
|
|
272
|
+
|
|
273
|
+
export function MessagesIsland() {
|
|
274
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
275
|
+
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
// 초기 데이터 로드
|
|
278
|
+
supabase.from("messages").select("*").then(({ data }) => {
|
|
279
|
+
setMessages(data || []);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Realtime 구독
|
|
283
|
+
const channel = supabase
|
|
284
|
+
.channel("messages")
|
|
285
|
+
.on(
|
|
286
|
+
"postgres_changes",
|
|
287
|
+
{ event: "INSERT", schema: "public", table: "messages" },
|
|
288
|
+
(payload) => {
|
|
289
|
+
setMessages((prev) => [...prev, payload.new as Message]);
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
.subscribe();
|
|
293
|
+
|
|
294
|
+
return () => {
|
|
295
|
+
supabase.removeChannel(channel);
|
|
296
|
+
};
|
|
297
|
+
}, []);
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<ul>
|
|
301
|
+
{messages.map((msg) => (
|
|
302
|
+
<li key={msg.id}>{msg.content}</li>
|
|
303
|
+
))}
|
|
304
|
+
</ul>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Storage 사용
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// 파일 업로드
|
|
313
|
+
const { data, error } = await supabase.storage
|
|
314
|
+
.from("avatars")
|
|
315
|
+
.upload(`${userId}/avatar.png`, file);
|
|
316
|
+
|
|
317
|
+
// 공개 URL 가져오기
|
|
318
|
+
const { data: { publicUrl } } = supabase.storage
|
|
319
|
+
.from("avatars")
|
|
320
|
+
.getPublicUrl(`${userId}/avatar.png`);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Reference: [Supabase Documentation](https://supabase.com/docs)
|