sonamu 0.10.5 → 0.10.7
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/api/base-frame.d.ts +2 -0
- package/dist/api/base-frame.d.ts.map +1 -1
- package/dist/api/base-frame.js +8 -1
- package/dist/api/decorators.js +1 -1
- package/dist/api/sonamu.js +1 -1
- package/dist/bin/cli.js +195 -187
- package/dist/cone/cone-generator.js +3 -9
- package/dist/database/base-model.js +2 -2
- package/dist/database/knex.d.ts.map +1 -1
- package/dist/database/knex.js +2 -2
- package/dist/database/puri.d.ts +17 -0
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +102 -3
- package/dist/entity/entity-manager.js +1 -1
- package/dist/index.js +3 -3
- package/dist/migration/code-generation.js +6 -6
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +52 -7
- package/dist/migration/slack-confirm.js +1 -1
- package/dist/naite/naite-reporter.js +1 -1
- package/dist/syncer/checksum.js +1 -1
- package/dist/syncer/code-generator.js +1 -1
- package/dist/syncer/entity-operations.js +1 -1
- package/dist/syncer/syncer-actions.js +1 -1
- package/dist/syncer/syncer.js +1 -1
- package/dist/ui-web/assets/{index-BLX4zN8c.js → index-D0MHYbxl.js} +1 -1
- package/dist/ui-web/assets/index-Dx_JX4aQ.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/dist/utils/formatter.js +1 -1
- package/package.json +3 -3
- package/src/api/base-frame.ts +14 -1
- package/src/bin/cli.ts +268 -280
- package/src/cone/cone-generator.ts +2 -18
- package/src/database/__tests__/puri.test.ts +183 -0
- package/src/database/knex.ts +4 -1
- package/src/database/puri.ts +229 -2
- package/src/database/puri.types.test-d.ts +56 -0
- package/src/migration/code-generation.ts +5 -5
- package/src/migration/migrator.ts +81 -7
- package/src/skills/AGENTS.md +76 -50
- package/src/skills/sonamu/SKILL.md +46 -227
- package/src/skills/{sonamu/ai-agents.md → sonamu-ai-agents/SKILL.md} +3 -3
- package/src/skills/{sonamu/api.md → sonamu-api/SKILL.md} +3 -2
- package/src/skills/{sonamu/auth.md → sonamu-auth/SKILL.md} +9 -4
- package/src/skills/{sonamu/auth-plugins.md → sonamu-auth/references/plugins.md} +2 -7
- package/src/skills/sonamu-auth/references/user-id-migration-followups.md +192 -0
- package/src/skills/{sonamu/auth-migration.md → sonamu-auth/references/user-id-migration.md} +1 -195
- package/src/skills/sonamu-config/SKILL.md +203 -0
- package/src/skills/{sonamu → sonamu-config/references}/database.md +3 -8
- package/src/skills/sonamu-config/references/environments.md +178 -0
- package/src/skills/sonamu-config/references/server-options.md +400 -0
- package/src/skills/sonamu-entity/SKILL.md +180 -0
- package/src/skills/{sonamu/entity-validation-checklist.md → sonamu-entity/references/creation-workflow.md} +117 -8
- package/src/skills/sonamu-entity/references/design-guides.md +243 -0
- package/src/skills/sonamu-entity/references/field-types.md +170 -0
- package/src/skills/sonamu-entity/references/relations-detail.md +245 -0
- package/src/skills/{sonamu/entity-relations.md → sonamu-entity/references/relations.md} +1 -258
- package/src/skills/{sonamu → sonamu-entity/references}/subset.md +1 -11
- package/src/skills/sonamu-fixture/SKILL.md +180 -0
- package/src/skills/{sonamu/fixture-cli.md → sonamu-fixture/references/cli-usage.md} +5 -176
- package/src/skills/{sonamu → sonamu-fixture/references}/cone.md +4 -14
- package/src/skills/sonamu-frontend/SKILL.md +142 -0
- package/src/skills/sonamu-frontend/references/components.md +323 -0
- package/src/skills/sonamu-frontend/references/examples.md +64 -0
- package/src/skills/sonamu-frontend/references/hooks.md +273 -0
- package/src/skills/sonamu-frontend/references/runtime.md +165 -0
- package/src/skills/{sonamu → sonamu-frontend/references}/scaffolding.md +3 -8
- package/src/skills/{sonamu/i18n.md → sonamu-i18n/SKILL.md} +2 -2
- package/src/skills/{sonamu/migration.md → sonamu-migration/SKILL.md} +1 -1
- package/src/skills/{sonamu/naite.md → sonamu-naite/SKILL.md} +5 -5
- package/src/skills/sonamu-query/SKILL.md +48 -0
- package/src/skills/sonamu-query/references/model-patterns.md +390 -0
- package/src/skills/{sonamu → sonamu-query/references}/model.md +1 -401
- package/src/skills/{sonamu → sonamu-query/references}/puri.md +31 -243
- package/src/skills/sonamu-query/references/search.md +238 -0
- package/src/skills/{sonamu → sonamu-query/references}/upsert.md +1 -6
- package/src/skills/{sonamu/tasks.md → sonamu-tasks/SKILL.md} +1 -1
- package/src/skills/sonamu-testing/SKILL.md +251 -0
- package/src/skills/{sonamu/testing-devrunner.md → sonamu-testing/references/devrunner.md} +4 -9
- package/src/skills/sonamu-testing/references/helpers.md +185 -0
- package/src/skills/sonamu-testing/references/patterns.md +263 -0
- package/src/skills/sonamu-testing/references/pitfalls.md +588 -0
- package/src/skills/sonamu-testing/references/quick-start.md +285 -0
- package/src/skills/sonamu-testing/references/type-safety.md +172 -0
- package/src/skills/sonamu-testing/references/writing-plan.md +375 -0
- package/src/skills/{sonamu/vector.md → sonamu-vector/SKILL.md} +1 -1
- package/dist/ui-web/assets/index-Dx4ap5i4.css +0 -1
- package/src/skills/commands/sonamu-skills.md +0 -20
- package/src/skills/project/README.md +0 -19
- package/src/skills/project/architecture.md +0 -373
- package/src/skills/sonamu/cdd.md +0 -129
- package/src/skills/sonamu/config.md +0 -772
- package/src/skills/sonamu/create-sonamu.md +0 -208
- package/src/skills/sonamu/entity-basic.md +0 -678
- package/src/skills/sonamu/framework-change.md +0 -96
- package/src/skills/sonamu/frontend.md +0 -915
- package/src/skills/sonamu/project-init.md +0 -477
- package/src/skills/sonamu/skill-contribution.md +0 -247
- package/src/skills/sonamu/testing.md +0 -2163
|
@@ -1,772 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sonamu-config
|
|
3
|
-
description: Sonamu project configuration. .env environment variables, sonamu.config.ts settings. Use when configuring a new Sonamu project.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Sonamu Project Configuration
|
|
7
|
-
|
|
8
|
-
A guide for setting up `.env` and `sonamu.config.ts` after project creation.
|
|
9
|
-
|
|
10
|
-
## .env File
|
|
11
|
-
|
|
12
|
-
Location: `packages/api/.env`
|
|
13
|
-
|
|
14
|
-
### Default Environment Variables (generated by create-sonamu)
|
|
15
|
-
|
|
16
|
-
```env
|
|
17
|
-
# Database Configuration
|
|
18
|
-
DB_HOST=0.0.0.0
|
|
19
|
-
DB_PORT=5432
|
|
20
|
-
DB_USER=postgres
|
|
21
|
-
DB_PASSWORD=1234
|
|
22
|
-
CONTAINER_NAME=myproject-container
|
|
23
|
-
DATABASE_NAME=myproject
|
|
24
|
-
PROJECT_NAME=myproject
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Additional Environment Variables (as needed)
|
|
28
|
-
|
|
29
|
-
```env
|
|
30
|
-
# Session (must be changed in production)
|
|
31
|
-
SESSION_SECRET=your-secret-key-change-in-production
|
|
32
|
-
SESSION_SALT=random-16-char-salt
|
|
33
|
-
|
|
34
|
-
# AWS S3 (for file uploads)
|
|
35
|
-
AWS_ACCESS_KEY_ID=your-access-key
|
|
36
|
-
AWS_SECRET_ACCESS_KEY=your-secret-key
|
|
37
|
-
S3_REGION=ap-northeast-2
|
|
38
|
-
S3_BUCKET=your-bucket-name
|
|
39
|
-
|
|
40
|
-
# Slack notifications (for migration confirmation)
|
|
41
|
-
SLACK_BOT_TOKEN=xoxb-your-token
|
|
42
|
-
SLACK_CHANNEL_ID=C0123456789
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Environment Variable Reference
|
|
46
|
-
|
|
47
|
-
| Variable | Required | Description |
|
|
48
|
-
| ----------------------- | -------- | --------------------------------------------------------- |
|
|
49
|
-
| `DB_HOST` | ✓ | DB host (Docker: `0.0.0.0`, external DB: the relevant IP) |
|
|
50
|
-
| `DB_PORT` | ✓ | DB port (default: `5432`) |
|
|
51
|
-
| `DB_USER` | ✓ | DB username (default: `postgres`) |
|
|
52
|
-
| `DB_PASSWORD` | ✓ | DB password |
|
|
53
|
-
| `DATABASE_NAME` | ✓ | Database name |
|
|
54
|
-
| `PROJECT_NAME` | ✓ | Project name (used in Docker, config) |
|
|
55
|
-
| `CONTAINER_NAME` | | Docker container name |
|
|
56
|
-
| `SESSION_SECRET` | | Session encryption key (required in production) |
|
|
57
|
-
| `SESSION_SALT` | | Session salt (16 characters) |
|
|
58
|
-
| `AWS_ACCESS_KEY_ID` | | Required when using S3 |
|
|
59
|
-
| `AWS_SECRET_ACCESS_KEY` | | Required when using S3 |
|
|
60
|
-
| `S3_REGION` | | S3 region (default: `ap-northeast-2`) |
|
|
61
|
-
| `S3_BUCKET` | | S3 bucket name |
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## sonamu.config.ts
|
|
66
|
-
|
|
67
|
-
Location: `packages/api/src/sonamu.config.ts`
|
|
68
|
-
|
|
69
|
-
### Full Structure
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import path from "node:path";
|
|
73
|
-
import { CachePresets, defineConfig } from "sonamu";
|
|
74
|
-
import { drivers as cacheDrivers, store } from "sonamu/cache";
|
|
75
|
-
import { drivers } from "sonamu/storage";
|
|
76
|
-
|
|
77
|
-
const host = "localhost";
|
|
78
|
-
const port = 34900;
|
|
79
|
-
|
|
80
|
-
export default defineConfig({
|
|
81
|
-
projectName: process.env.PROJECT_NAME ?? "MyProject",
|
|
82
|
-
api: {
|
|
83
|
-
/* API settings */
|
|
84
|
-
},
|
|
85
|
-
i18n: {
|
|
86
|
-
/* i18n settings */
|
|
87
|
-
},
|
|
88
|
-
sync: {
|
|
89
|
-
/* sync settings */
|
|
90
|
-
},
|
|
91
|
-
database: {
|
|
92
|
-
/* DB settings */
|
|
93
|
-
},
|
|
94
|
-
logging: {
|
|
95
|
-
/* logging settings (disable with false) */
|
|
96
|
-
},
|
|
97
|
-
test: {
|
|
98
|
-
/* test settings */
|
|
99
|
-
},
|
|
100
|
-
server: {
|
|
101
|
-
/* server settings */
|
|
102
|
-
},
|
|
103
|
-
slackConfirm: {
|
|
104
|
-
/* Production migration Slack approval */
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Key Section Settings
|
|
110
|
-
|
|
111
|
-
#### projectName
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
projectName: process.env.PROJECT_NAME ?? "MyProject",
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
#### api
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
api: {
|
|
121
|
-
dir: "api", // API directory name
|
|
122
|
-
timezone: "Asia/Seoul", // timezone
|
|
123
|
-
route: {
|
|
124
|
-
prefix: "/api", // API route prefix
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
#### i18n
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
i18n: {
|
|
133
|
-
defaultLocale: "ko",
|
|
134
|
-
supportedLocales: ["ko", "en"],
|
|
135
|
-
},
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
See `i18n.md` for details.
|
|
139
|
-
|
|
140
|
-
#### sync
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
sync: {
|
|
144
|
-
targets: ["web"], // target packages for type sync
|
|
145
|
-
},
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
#### database
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
database: {
|
|
152
|
-
database: "pg", // PostgreSQL
|
|
153
|
-
name: process.env.DATABASE_NAME ?? "database_name",
|
|
154
|
-
defaultOptions: {
|
|
155
|
-
connection: {
|
|
156
|
-
host: process.env.DB_HOST || "0.0.0.0",
|
|
157
|
-
port: Number(process.env.DB_PORT) || 5432,
|
|
158
|
-
user: process.env.DB_USER || "postgres",
|
|
159
|
-
password: process.env.DB_PASSWORD,
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
See `database.md` for details.
|
|
166
|
-
|
|
167
|
-
#### test
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
test: {
|
|
171
|
-
parallel: true, // enable parallel testing
|
|
172
|
-
maxWorkers: 4, // number of workers
|
|
173
|
-
devRunner: { // Vitest resident instance inside Dev server
|
|
174
|
-
enabled: true,
|
|
175
|
-
watch: true,
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
See `testing.md`, `testing-devrunner.md` for details.
|
|
181
|
-
|
|
182
|
-
#### server
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
server: {
|
|
186
|
-
listen: { port: 34900, host: "localhost" },
|
|
187
|
-
plugins: { /* plugin settings */ },
|
|
188
|
-
auth: true,
|
|
189
|
-
apiConfig: { /* API settings */ },
|
|
190
|
-
storage: { /* storage settings */ },
|
|
191
|
-
cache: { /* cache settings */ },
|
|
192
|
-
lifecycle: { /* lifecycle hooks */ },
|
|
193
|
-
},
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## server.auth Details (better-auth Authentication)
|
|
199
|
-
|
|
200
|
-
Sonamu provides an authentication system using **better-auth**.
|
|
201
|
-
|
|
202
|
-
### 1. Auto-generate Entities
|
|
203
|
-
|
|
204
|
-
```bash
|
|
205
|
-
pnpm sonamu auth generate
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Generated entities:
|
|
209
|
-
|
|
210
|
-
- **User** - user (id, name, email, email_verified, image)
|
|
211
|
-
- **Session** - session (token, expires_at, user_id)
|
|
212
|
-
- **Account** - account (provider_id, access_token, etc.)
|
|
213
|
-
- **Verification** - email verification
|
|
214
|
-
|
|
215
|
-
### 2. server.auth Configuration
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
server: {
|
|
219
|
-
// Basic configuration (emailAndPassword enabled)
|
|
220
|
-
auth: {
|
|
221
|
-
emailAndPassword: { enabled: true },
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
// Add social login
|
|
225
|
-
// auth: {
|
|
226
|
-
// emailAndPassword: { enabled: true },
|
|
227
|
-
// socialProviders: {
|
|
228
|
-
// google: {
|
|
229
|
-
// clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
230
|
-
// clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
231
|
-
// },
|
|
232
|
-
// },
|
|
233
|
-
// },
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### 3. Authentication API Endpoints
|
|
238
|
-
|
|
239
|
-
Automatically registered under the `/api/auth/*` path:
|
|
240
|
-
|
|
241
|
-
| Endpoint | Method | Description |
|
|
242
|
-
| ------------------------- | ------ | ----------- |
|
|
243
|
-
| `/api/auth/sign-up/email` | POST | Sign up |
|
|
244
|
-
| `/api/auth/sign-in/email` | POST | Sign in |
|
|
245
|
-
| `/api/auth/sign-out` | POST | Sign out |
|
|
246
|
-
| `/api/auth/get-session` | GET | Get session |
|
|
247
|
-
|
|
248
|
-
### 4. Accessing user/session from Context
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
@api({ httpMethod: "GET", guards: ["user"] })
|
|
252
|
-
async me(): Promise<UserSubsetA | null> {
|
|
253
|
-
const { user, session } = Sonamu.getContext();
|
|
254
|
-
if (!user) return null;
|
|
255
|
-
return this.findById("A", user.id);
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### 5. Field Mapping (camelCase → snake_case)
|
|
260
|
-
|
|
261
|
-
better-auth uses camelCase, Sonamu uses snake_case. Automatic mapping is applied:
|
|
262
|
-
|
|
263
|
-
| better-auth | Sonamu |
|
|
264
|
-
| --------------- | ---------------- |
|
|
265
|
-
| `emailVerified` | `email_verified` |
|
|
266
|
-
| `createdAt` | `created_at` |
|
|
267
|
-
| `userId` | `user_id` |
|
|
268
|
-
| `expiresAt` | `expires_at` |
|
|
269
|
-
|
|
270
|
-
---
|
|
271
|
-
|
|
272
|
-
## Guards System (Access Control)
|
|
273
|
-
|
|
274
|
-
The Sonamu permission system consists of 2 components:
|
|
275
|
-
|
|
276
|
-
1. **GuardKeys** - permission key definitions
|
|
277
|
-
2. **guardHandler** - permission check logic
|
|
278
|
-
|
|
279
|
-
### 1. Extending GuardKeys (Custom Permissions)
|
|
280
|
-
|
|
281
|
-
**Source code:** `modules/sonamu/src/api/decorators.ts` (GuardKeys interface)
|
|
282
|
-
|
|
283
|
-
Provided by default: `query`, `admin`, `user`
|
|
284
|
-
|
|
285
|
-
To add custom permissions, extend in `src/typings/sonamu.d.ts`:
|
|
286
|
-
|
|
287
|
-
**File location:** `src/typings/sonamu.d.ts`
|
|
288
|
-
|
|
289
|
-
```typescript
|
|
290
|
-
import {} from "sonamu";
|
|
291
|
-
|
|
292
|
-
declare module "sonamu" {
|
|
293
|
-
export interface GuardKeys {
|
|
294
|
-
query: true;
|
|
295
|
-
admin: true;
|
|
296
|
-
user: true;
|
|
297
|
-
manager: true; // added
|
|
298
|
-
superadmin: true; // added
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### 2. Using guards in the @api Decorator
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
// user.model.ts
|
|
307
|
-
import { api } from "sonamu";
|
|
308
|
-
|
|
309
|
-
class UserModelClass extends BaseModelClass {
|
|
310
|
-
@api({ httpMethod: "GET", guards: ["user"] })
|
|
311
|
-
async me(): Promise<UserSubsetA | null> {
|
|
312
|
-
// only logged-in users can access
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
@api({ httpMethod: "DELETE", guards: ["admin"] })
|
|
316
|
-
async del(ids: number[]): Promise<number> {
|
|
317
|
-
// only admins can access
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
@api({ httpMethod: "GET", guards: ["admin", "manager"] })
|
|
321
|
-
async adminList(): Promise<UserSubsetA[]> {
|
|
322
|
-
// admin or manager permission
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### 3. Implementing guardHandler
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
import { Sonamu } from "sonamu";
|
|
331
|
-
|
|
332
|
-
// sonamu.config.ts
|
|
333
|
-
apiConfig: {
|
|
334
|
-
guardHandler: (guard, request, api) => {
|
|
335
|
-
// Access user from better-auth Context
|
|
336
|
-
const { user } = Sonamu.getContext();
|
|
337
|
-
|
|
338
|
-
switch (guard) {
|
|
339
|
-
case "user":
|
|
340
|
-
// login required
|
|
341
|
-
if (!user) {
|
|
342
|
-
throw new Error("Login is required");
|
|
343
|
-
}
|
|
344
|
-
break;
|
|
345
|
-
|
|
346
|
-
case "admin":
|
|
347
|
-
// admin permission (requires adding role field to User entity)
|
|
348
|
-
if (!user || (user as any).role !== "admin") {
|
|
349
|
-
throw new Error("Only admins can access");
|
|
350
|
-
}
|
|
351
|
-
break;
|
|
352
|
-
|
|
353
|
-
case "manager":
|
|
354
|
-
// manager permission (custom Guard example)
|
|
355
|
-
if (!user || !["admin", "manager"].includes((user as any).role)) {
|
|
356
|
-
throw new Error("Manager permission is required");
|
|
357
|
-
}
|
|
358
|
-
break;
|
|
359
|
-
|
|
360
|
-
case "query":
|
|
361
|
-
// allow all users (including unauthenticated)
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
**NOTE:** better-auth's default User entity does not have a `role` field. If role-based authentication is needed, add a `role` field to the User entity or create a separate Role entity.
|
|
369
|
-
|
|
370
|
-
### Menu/Screen Access Control by Permission
|
|
371
|
-
|
|
372
|
-
UI access control by permission is handled **on the frontend**:
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
// web/src/lib/auth.ts
|
|
376
|
-
export const menuPermissions = {
|
|
377
|
-
dashboard: ["user", "admin", "manager"],
|
|
378
|
-
userManagement: ["admin"],
|
|
379
|
-
settings: ["admin", "manager"],
|
|
380
|
-
reports: ["admin", "manager"],
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
export function canAccess(userRole: string, menu: keyof typeof menuPermissions) {
|
|
384
|
-
return menuPermissions[menu].includes(userRole);
|
|
385
|
-
}
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
```tsx
|
|
389
|
-
// web/src/components/Sidebar.tsx
|
|
390
|
-
{
|
|
391
|
-
canAccess(user.role, "userManagement") && (
|
|
392
|
-
<MenuItem href="/admin/users">User Management</MenuItem>
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## server.plugins Details
|
|
400
|
-
|
|
401
|
-
### session (Session Management)
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
session: {
|
|
405
|
-
secret: process.env.SESSION_SECRET || "change-this-in-production",
|
|
406
|
-
salt: process.env.SESSION_SALT || "mq9hDxBCDbsQDR6N",
|
|
407
|
-
cookie: {
|
|
408
|
-
domain: "localhost", // change to actual domain in production
|
|
409
|
-
path: "/",
|
|
410
|
-
maxAge: 60 * 60 * 24 * 365 * 10, // 10 years
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
**Production checklist:**
|
|
416
|
-
|
|
417
|
-
- `SESSION_SECRET`: must be changed to a strong random string
|
|
418
|
-
- `SESSION_SALT`: change to a 16-character random string
|
|
419
|
-
- `cookie.domain`: change to the actual domain
|
|
420
|
-
|
|
421
|
-
### static (Static Files)
|
|
422
|
-
|
|
423
|
-
```typescript
|
|
424
|
-
static: {
|
|
425
|
-
root: path.join(import.meta.dirname, "/../", "public"),
|
|
426
|
-
prefix: "/api/public",
|
|
427
|
-
},
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### multipart (File Upload)
|
|
431
|
-
|
|
432
|
-
```typescript
|
|
433
|
-
multipart: {
|
|
434
|
-
limits: {
|
|
435
|
-
fileSize: 1024 * 1024 * 30, // 30MB
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
## server.storage Details
|
|
443
|
-
|
|
444
|
-
### Local File System
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
storage: {
|
|
448
|
-
drivers: {
|
|
449
|
-
fs: drivers.fs({
|
|
450
|
-
location: path.join(import.meta.dirname, "/../public/uploaded"),
|
|
451
|
-
visibility: "public",
|
|
452
|
-
urlBuilder: {
|
|
453
|
-
async generateURL(key) {
|
|
454
|
-
return `/api/public/uploaded/${key}`;
|
|
455
|
-
},
|
|
456
|
-
async generateSignedURL(key) {
|
|
457
|
-
return `/api/public/uploaded/${key}`;
|
|
458
|
-
},
|
|
459
|
-
},
|
|
460
|
-
}),
|
|
461
|
-
},
|
|
462
|
-
},
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
### AWS S3
|
|
466
|
-
|
|
467
|
-
```typescript
|
|
468
|
-
storage: {
|
|
469
|
-
drivers: {
|
|
470
|
-
s3: drivers.s3({
|
|
471
|
-
credentials: {
|
|
472
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
|
|
473
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
|
|
474
|
-
},
|
|
475
|
-
region: process.env.S3_REGION ?? "ap-northeast-2",
|
|
476
|
-
bucket: process.env.S3_BUCKET ?? "my-bucket",
|
|
477
|
-
visibility: "private",
|
|
478
|
-
}),
|
|
479
|
-
},
|
|
480
|
-
},
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
---
|
|
484
|
-
|
|
485
|
-
## server.cache Details
|
|
486
|
-
|
|
487
|
-
Sonamu uses **BentoCache**.
|
|
488
|
-
|
|
489
|
-
```typescript
|
|
490
|
-
import { drivers as cacheDrivers, store } from "sonamu/cache";
|
|
491
|
-
|
|
492
|
-
cache: {
|
|
493
|
-
default: "main",
|
|
494
|
-
stores: {
|
|
495
|
-
main: store().useL1Layer(cacheDrivers.memory({ maxSize: "50mb" })),
|
|
496
|
-
},
|
|
497
|
-
ttl: "5m",
|
|
498
|
-
prefix: "",
|
|
499
|
-
},
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
**Available drivers:**
|
|
503
|
-
|
|
504
|
-
- `memory` - in-memory cache (default)
|
|
505
|
-
- `file` - file-based cache
|
|
506
|
-
- `redis` - Redis cache
|
|
507
|
-
- `knex` - DB-based cache
|
|
508
|
-
|
|
509
|
-
For other drivers, refer to the [BentoCache documentation](https://bentocache.dev/).
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
|
-
## server.apiConfig Details
|
|
514
|
-
|
|
515
|
-
### contextProvider
|
|
516
|
-
|
|
517
|
-
Inject additional information into Context per request:
|
|
518
|
-
|
|
519
|
-
```typescript
|
|
520
|
-
contextProvider: (defaultContext, request) => {
|
|
521
|
-
return {
|
|
522
|
-
...defaultContext,
|
|
523
|
-
ip: request.ip,
|
|
524
|
-
session: request.session,
|
|
525
|
-
body: request.body,
|
|
526
|
-
// custom fields can be added
|
|
527
|
-
};
|
|
528
|
-
},
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
### guardHandler
|
|
532
|
-
|
|
533
|
-
Handle API guard processing:
|
|
534
|
-
|
|
535
|
-
```typescript
|
|
536
|
-
guardHandler: (guard, request, api) => {
|
|
537
|
-
// access control based on guard value
|
|
538
|
-
if (guard === "admin" && request.user?.role !== "admin") {
|
|
539
|
-
throw new Error("Only admins can access");
|
|
540
|
-
}
|
|
541
|
-
},
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### cacheControlHandler
|
|
545
|
-
|
|
546
|
-
Set HTTP cache headers:
|
|
547
|
-
|
|
548
|
-
```typescript
|
|
549
|
-
cacheControlHandler: (req) => {
|
|
550
|
-
switch (req.type) {
|
|
551
|
-
case "assets":
|
|
552
|
-
if (req.path.match(/-[a-f0-9]+\./)) {
|
|
553
|
-
return CachePresets.immutable; // files with hash
|
|
554
|
-
}
|
|
555
|
-
return CachePresets.longLived;
|
|
556
|
-
|
|
557
|
-
case "api":
|
|
558
|
-
if (req.method === "GET") {
|
|
559
|
-
return CachePresets.shortLived;
|
|
560
|
-
}
|
|
561
|
-
return CachePresets.noCache;
|
|
562
|
-
|
|
563
|
-
case "ssr":
|
|
564
|
-
return CachePresets.ssr;
|
|
565
|
-
|
|
566
|
-
case "csr":
|
|
567
|
-
return CachePresets.shortLived;
|
|
568
|
-
}
|
|
569
|
-
},
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
---
|
|
573
|
-
|
|
574
|
-
## server.lifecycle Details
|
|
575
|
-
|
|
576
|
-
```typescript
|
|
577
|
-
lifecycle: {
|
|
578
|
-
onStart: () => {
|
|
579
|
-
console.log(`🌲 Server listening on http://${host}:${port}`);
|
|
580
|
-
},
|
|
581
|
-
onShutdown: () => {
|
|
582
|
-
console.log("graceful shutdown");
|
|
583
|
-
// close DB connections, clean up resources, etc.
|
|
584
|
-
},
|
|
585
|
-
onError: (error, request, reply) => {
|
|
586
|
-
console.error(error);
|
|
587
|
-
reply.status(500).send({
|
|
588
|
-
name: error.name,
|
|
589
|
-
message: error.message,
|
|
590
|
-
});
|
|
591
|
-
},
|
|
592
|
-
},
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
---
|
|
596
|
-
|
|
597
|
-
## Sonamu Local Development Environment Setup
|
|
598
|
-
|
|
599
|
-
**When is this needed:**
|
|
600
|
-
|
|
601
|
-
- When modifying the Sonamu framework source code during development
|
|
602
|
-
- When linking a local Sonamu repository to a project for development
|
|
603
|
-
|
|
604
|
-
**Problem:**
|
|
605
|
-
|
|
606
|
-
When linking Sonamu with pnpm link, type errors occur at build time:
|
|
607
|
-
|
|
608
|
-
```
|
|
609
|
-
error TS2345: Argument of type 'ZodNumber' is not assignable to parameter...
|
|
610
|
-
Type '2' is not assignable to type '3'.
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
**Cause:**
|
|
614
|
-
|
|
615
|
-
- The linked Sonamu and the project each maintain their own `node_modules`
|
|
616
|
-
- TypeScript type mismatches occur due to different versions of shared dependencies (e.g. zod)
|
|
617
|
-
- TypeScript simultaneously references two different type definitions, causing errors
|
|
618
|
-
|
|
619
|
-
**Solution:**
|
|
620
|
-
|
|
621
|
-
### 1. Add override to pnpm-workspace.yaml
|
|
622
|
-
|
|
623
|
-
In the project root's `pnpm-workspace.yaml`:
|
|
624
|
-
|
|
625
|
-
```yaml
|
|
626
|
-
overrides:
|
|
627
|
-
sonamu: link:../../sonamu/modules/sonamu
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
### 2. Specify published version in packages/api/package.json
|
|
631
|
-
|
|
632
|
-
```json
|
|
633
|
-
{
|
|
634
|
-
"dependencies": {
|
|
635
|
-
"sonamu": "^0.7.45" // specify the latest published version
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
### 3. Run install
|
|
641
|
-
|
|
642
|
-
```bash
|
|
643
|
-
pnpm install
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
### 4. Verify build
|
|
647
|
-
|
|
648
|
-
```bash
|
|
649
|
-
cd packages/api
|
|
650
|
-
pnpm build
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
**How it works:**
|
|
654
|
-
|
|
655
|
-
- **TypeScript type check**: references type definitions from the npm registry based on the published version in `package.json`
|
|
656
|
-
- **Actual runtime**: `pnpm overrides` local link takes priority and runs local source code
|
|
657
|
-
- Separates type checking and runtime to resolve version mismatch issues
|
|
658
|
-
|
|
659
|
-
**Notes:**
|
|
660
|
-
|
|
661
|
-
- Changes to Sonamu source code are immediately reflected in the project
|
|
662
|
-
- Restarting the project is required after building Sonamu
|
|
663
|
-
- For general project development, using the npm version is recommended
|
|
664
|
-
|
|
665
|
-
---
|
|
666
|
-
|
|
667
|
-
## Environment-Specific Configuration
|
|
668
|
-
|
|
669
|
-
### Development Environment
|
|
670
|
-
|
|
671
|
-
```env
|
|
672
|
-
# packages/api/.env
|
|
673
|
-
DB_HOST=0.0.0.0
|
|
674
|
-
DB_PORT=5432
|
|
675
|
-
DB_USER=postgres
|
|
676
|
-
DB_PASSWORD=1234
|
|
677
|
-
DATABASE_NAME=myproject
|
|
678
|
-
PROJECT_NAME=myproject
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
### Production Environment
|
|
682
|
-
|
|
683
|
-
```env
|
|
684
|
-
# packages/api/.env.production
|
|
685
|
-
DB_HOST=your-rds-endpoint.amazonaws.com
|
|
686
|
-
DB_PORT=5432
|
|
687
|
-
DB_USER=produser
|
|
688
|
-
DB_PASSWORD=strong-password-here
|
|
689
|
-
DATABASE_NAME=myproject_prod
|
|
690
|
-
PROJECT_NAME=myproject
|
|
691
|
-
|
|
692
|
-
SESSION_SECRET=very-long-random-string-at-least-32-chars
|
|
693
|
-
SESSION_SALT=random16charstr!
|
|
694
|
-
|
|
695
|
-
AWS_ACCESS_KEY_ID=AKIA...
|
|
696
|
-
AWS_SECRET_ACCESS_KEY=...
|
|
697
|
-
S3_REGION=ap-northeast-2
|
|
698
|
-
S3_BUCKET=myproject-prod-bucket
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
---
|
|
702
|
-
|
|
703
|
-
## server Additional Options
|
|
704
|
-
|
|
705
|
-
### baseUrl
|
|
706
|
-
|
|
707
|
-
```typescript
|
|
708
|
-
server: {
|
|
709
|
-
baseUrl: "https://api.example.com", // external access URL (default: host:port)
|
|
710
|
-
}
|
|
711
|
-
```
|
|
712
|
-
|
|
713
|
-
### fastify
|
|
714
|
-
|
|
715
|
-
Pass Fastify server options directly (excluding `logger`).
|
|
716
|
-
|
|
717
|
-
### Full Plugin List
|
|
718
|
-
|
|
719
|
-
| Plugin | Type | Description |
|
|
720
|
-
| ----------- | ------------------------------------ | ---------------------------------------- |
|
|
721
|
-
| `compress` | `boolean \| FastifyCompressOptions` | Response compression (@fastify/compress) |
|
|
722
|
-
| `cors` | `boolean \| FastifyCorsOptions` | CORS configuration |
|
|
723
|
-
| `formbody` | `boolean \| FastifyFormbodyOptions` | x-www-form-urlencoded parsing |
|
|
724
|
-
| `multipart` | `boolean \| FastifyMultipartOptions` | File upload |
|
|
725
|
-
| `qs` | `boolean \| QsPluginOptions` | Query string parsing |
|
|
726
|
-
| `session` | session config | Session management |
|
|
727
|
-
| `sse` | `boolean \| SsePluginOptions` | Server-Sent Events |
|
|
728
|
-
| `static` | `boolean \| FastifyStaticOptions` | Static file serving |
|
|
729
|
-
| `custom` | `(server: FastifyInstance) => void` | Custom plugin registration function |
|
|
730
|
-
|
|
731
|
-
## logging
|
|
732
|
-
|
|
733
|
-
Define logging configuration. Set to `false` to completely disable logging.
|
|
734
|
-
|
|
735
|
-
```typescript
|
|
736
|
-
logging: false, // disable logging
|
|
737
|
-
// or
|
|
738
|
-
logging: {
|
|
739
|
-
sinks: { /* define log output targets */ },
|
|
740
|
-
filters: { /* define filters */ },
|
|
741
|
-
},
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
## slackConfirm
|
|
745
|
-
|
|
746
|
-
Activates a Slack-based approval process for production DB migrations.
|
|
747
|
-
|
|
748
|
-
```typescript
|
|
749
|
-
slackConfirm: {
|
|
750
|
-
targets: ["production"], // list of DB keys requiring approval
|
|
751
|
-
botToken: process.env.SLACK_BOT_TOKEN ?? "", // Slack Bot Token (xoxb-...)
|
|
752
|
-
channelId: process.env.SLACK_CHANNEL_ID ?? "", // Slack Channel ID (C...)
|
|
753
|
-
},
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
---
|
|
757
|
-
|
|
758
|
-
## Post-Configuration Checklist
|
|
759
|
-
|
|
760
|
-
1. Confirm `.env` file is created
|
|
761
|
-
2. Start Docker: `pnpm docker:up`
|
|
762
|
-
3. Verify build: `pnpm build`
|
|
763
|
-
4. Start server: `pnpm dev`
|
|
764
|
-
5. Access Sonamu UI: http://localhost:34900/sonamu-ui
|
|
765
|
-
|
|
766
|
-
Before production deployment:
|
|
767
|
-
|
|
768
|
-
- [ ] Change `SESSION_SECRET`
|
|
769
|
-
- [ ] Change `SESSION_SALT`
|
|
770
|
-
- [ ] Change `cookie.domain` to the actual domain
|
|
771
|
-
- [ ] Configure S3 (if needed)
|
|
772
|
-
- [ ] Add error handling logic
|