sonamu 0.8.24 → 0.8.26
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/__tests__/config.test.js +189 -0
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +7 -2
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +14 -10
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/knex-adapter.d.ts +23 -0
- package/dist/auth/knex-adapter.d.ts.map +1 -0
- package/dist/auth/knex-adapter.js +163 -0
- package/dist/auth/plugins/wrappers/admin.d.ts +2 -2
- package/dist/bin/__tests__/ts-loader-register.test.js +45 -0
- package/dist/bin/cli.js +47 -9
- package/dist/bin/ts-loader-register.js +3 -29
- package/dist/bin/ts-loader-registration.d.ts +2 -0
- package/dist/bin/ts-loader-registration.d.ts.map +1 -0
- package/dist/bin/ts-loader-registration.js +42 -0
- package/dist/cone/cone-generator.js +3 -3
- package/dist/database/puri-subset.test-d.js +9 -1
- package/dist/database/puri-subset.types.d.ts +1 -1
- package/dist/database/puri-subset.types.d.ts.map +1 -1
- package/dist/database/puri-subset.types.js +1 -1
- package/dist/testing/fixture-generator.js +5 -5
- package/dist/ui/ai-client.js +2 -2
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +14 -14
- package/dist/ui/cdd-service.d.ts +15 -18
- package/dist/ui/cdd-service.d.ts.map +1 -1
- package/dist/ui/cdd-service.js +246 -222
- package/dist/ui/cdd-types.d.ts +41 -68
- package/dist/ui/cdd-types.d.ts.map +1 -1
- package/dist/ui/cdd-types.js +2 -2
- package/dist/ui-web/assets/index-CKo0Z2Iu.css +1 -0
- package/dist/ui-web/assets/{index-CxiydzeC.js → index-DK-2aacv.js} +83 -83
- package/dist/ui-web/index.html +2 -2
- package/package.json +6 -2
- package/src/api/__tests__/config.test.ts +225 -0
- package/src/api/config.ts +10 -4
- package/src/api/sonamu.ts +16 -13
- package/src/auth/index.ts +1 -0
- package/src/auth/knex-adapter.ts +208 -0
- package/src/bin/__tests__/ts-loader-register.test.ts +62 -0
- package/src/bin/cli.ts +52 -9
- package/src/bin/ts-loader-register.ts +2 -32
- package/src/bin/ts-loader-registration.ts +55 -0
- package/src/cone/cone-generator.ts +2 -2
- package/src/database/puri-subset.test-d.ts +102 -0
- package/src/database/puri-subset.types.ts +1 -1
- package/src/skills/commands/sonamu-skills.md +20 -0
- package/src/skills/sonamu/SKILL.md +179 -137
- package/src/skills/sonamu/ai-agents.md +69 -69
- package/src/skills/sonamu/api.md +147 -147
- package/src/skills/sonamu/auth-migration.md +220 -220
- package/src/skills/sonamu/auth-plugins.md +83 -83
- package/src/skills/sonamu/auth.md +106 -106
- package/src/skills/sonamu/cdd.md +65 -200
- package/src/skills/sonamu/cone.md +138 -138
- package/src/skills/sonamu/config.md +191 -191
- package/src/skills/sonamu/create-sonamu.md +66 -66
- package/src/skills/sonamu/database.md +158 -158
- package/src/skills/sonamu/entity-basic.md +292 -293
- package/src/skills/sonamu/entity-relations.md +246 -246
- package/src/skills/sonamu/entity-validation-checklist.md +124 -124
- package/src/skills/sonamu/fixture-cli.md +231 -231
- package/src/skills/sonamu/framework-change.md +37 -37
- package/src/skills/sonamu/frontend.md +223 -223
- package/src/skills/sonamu/i18n.md +82 -82
- package/src/skills/sonamu/migration.md +77 -77
- package/src/skills/sonamu/model.md +222 -222
- package/src/skills/sonamu/naite.md +86 -86
- package/src/skills/sonamu/project-init.md +228 -228
- package/src/skills/sonamu/puri.md +122 -122
- package/src/skills/sonamu/scaffolding.md +154 -154
- package/src/skills/sonamu/skill-contribution.md +124 -124
- package/src/skills/sonamu/subset.md +46 -46
- package/src/skills/sonamu/tasks.md +82 -82
- package/src/skills/sonamu/testing-devrunner.md +147 -147
- package/src/skills/sonamu/testing.md +673 -673
- package/src/skills/sonamu/upsert.md +79 -79
- package/src/skills/sonamu/vector.md +67 -67
- package/src/testing/fixture-generator.ts +4 -4
- package/src/ui/ai-client.ts +1 -1
- package/src/ui/api.ts +18 -17
- package/src/ui/cdd-service.ts +264 -254
- package/src/ui/cdd-types.ts +40 -75
- package/dist/ui-web/assets/index-BrQKU3j9.css +0 -1
- package/src/skills/sonamu/workflow.md +0 -317
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sonamu-i18n
|
|
3
|
-
description: Sonamu
|
|
3
|
+
description: Sonamu internationalization (i18n) support. Dictionary configuration, SD function usage, helper functions. Use when implementing internationalization.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# i18n (
|
|
6
|
+
# i18n (Internationalization)
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Configuration
|
|
9
9
|
|
|
10
10
|
`sonamu.config.ts`:
|
|
11
11
|
|
|
@@ -19,18 +19,18 @@ export default defineConfig({
|
|
|
19
19
|
});
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## File Structure
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
packages/api/src/i18n/
|
|
26
|
-
├── ko.ts #
|
|
27
|
-
├── en.ts #
|
|
28
|
-
└── sd.generated.ts #
|
|
26
|
+
├── ko.ts # Default locale (defaultLocale)
|
|
27
|
+
├── en.ts # Additional locale
|
|
28
|
+
└── sd.generated.ts # Auto-generated (pnpm sync)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## Dictionary Files
|
|
32
32
|
|
|
33
|
-
###
|
|
33
|
+
### Default locale (ko.ts)
|
|
34
34
|
|
|
35
35
|
```typescript
|
|
36
36
|
import { createFormat, josa } from "sonamu/dict";
|
|
@@ -46,7 +46,7 @@ export default {
|
|
|
46
46
|
} as const;
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
###
|
|
49
|
+
### Additional locale (en.ts)
|
|
50
50
|
|
|
51
51
|
```typescript
|
|
52
52
|
import { plural } from "sonamu/dict";
|
|
@@ -61,51 +61,51 @@ export default defineLocale({
|
|
|
61
61
|
});
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
## SD
|
|
64
|
+
## Using the SD Function
|
|
65
65
|
|
|
66
|
-
###
|
|
66
|
+
### Basic Usage
|
|
67
67
|
|
|
68
68
|
```typescript
|
|
69
69
|
import { SD } from "@/i18n/sd.generated";
|
|
70
70
|
|
|
71
|
-
//
|
|
71
|
+
// Simple string
|
|
72
72
|
SD("common.save"); // → "저장" (ko) / "Save" (en)
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// Function form (with parameters)
|
|
75
75
|
SD("validation.required")("이메일"); // → "이메일은 필수입니다"
|
|
76
76
|
SD("common.results")(5); // → "5개 결과" (ko) / "5 results" (en)
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
###
|
|
79
|
+
### Force a Specific Locale
|
|
80
80
|
|
|
81
81
|
```typescript
|
|
82
82
|
const EN = SD.locale("en");
|
|
83
|
-
EN("common.save"); // → "Save" (
|
|
83
|
+
EN("common.save"); // → "Save" (always English)
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
### Enum
|
|
86
|
+
### Enum Labels
|
|
87
87
|
|
|
88
88
|
```typescript
|
|
89
|
-
// entity.json
|
|
89
|
+
// Enum labels defined in entity.json
|
|
90
90
|
SD.enumLabels("ProjectStatus")["in_progress"]; // → "진행중"
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
##
|
|
93
|
+
## Helper Functions
|
|
94
94
|
|
|
95
|
-
### plural (
|
|
95
|
+
### plural (pluralization)
|
|
96
96
|
|
|
97
97
|
```typescript
|
|
98
98
|
import { plural } from "sonamu/dict";
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Handle singular/plural in English
|
|
101
101
|
plural(count, {
|
|
102
102
|
zero: "No items", // count === 0
|
|
103
103
|
one: "1 item", // count === 1
|
|
104
|
-
other: `${count} items`, //
|
|
104
|
+
other: `${count} items`, // everything else
|
|
105
105
|
});
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
### josa (
|
|
108
|
+
### josa (Korean postposition helper)
|
|
109
109
|
|
|
110
110
|
```typescript
|
|
111
111
|
import { josa } from "sonamu/dict";
|
|
@@ -117,7 +117,7 @@ josa("회사", "과와"); // → "회사와"
|
|
|
117
117
|
josa("서울", "으로"); // → "서울로"
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
### createFormat (
|
|
120
|
+
### createFormat (number/date formatting)
|
|
121
121
|
|
|
122
122
|
```typescript
|
|
123
123
|
import { createFormat } from "sonamu/dict";
|
|
@@ -127,17 +127,17 @@ format.number(1234567); // → "1,234,567"
|
|
|
127
127
|
format.date(new Date()); // → "2024. 1. 15."
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
## Entity
|
|
130
|
+
## Entity Integration
|
|
131
131
|
|
|
132
|
-
Entity
|
|
132
|
+
Entity title, prop descriptions, and enum labels are automatically included in the dictionary.
|
|
133
133
|
|
|
134
134
|
```
|
|
135
|
-
entity.{EntityId} → Entity
|
|
136
|
-
entity.{EntityId}.{propName} →
|
|
137
|
-
enum.{EnumId}.{value} → Enum
|
|
135
|
+
entity.{EntityId} → Entity title
|
|
136
|
+
entity.{EntityId}.{propName} → Property description
|
|
137
|
+
enum.{EnumId}.{value} → Enum value label
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
Example:
|
|
141
141
|
```typescript
|
|
142
142
|
SD("entity.User"); // → "사용자"
|
|
143
143
|
SD("entity.User.email"); // → "이메일"
|
|
@@ -146,7 +146,7 @@ SD("enum.UserRole.admin"); // → "관리자"
|
|
|
146
146
|
|
|
147
147
|
## localizedColumn
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
Returns the value for the current locale when locale-specific columns exist in the DB:
|
|
150
150
|
|
|
151
151
|
```typescript
|
|
152
152
|
import { localizedColumn } from "@/i18n/sd.generated";
|
|
@@ -155,98 +155,98 @@ import { localizedColumn } from "@/i18n/sd.generated";
|
|
|
155
155
|
localizedColumn(tag, "name"); // → "태그" (ko) / "Tag" (en)
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
## Sonamu UI
|
|
158
|
+
## Sonamu UI Management
|
|
159
159
|
|
|
160
|
-
`http://localhost:34900/sonamu-ui` → i18n
|
|
161
|
-
-
|
|
162
|
-
-
|
|
160
|
+
`http://localhost:34900/sonamu-ui` → i18n tab:
|
|
161
|
+
- Browse/edit dictionary
|
|
162
|
+
- Add/delete keys
|
|
163
163
|
- Excel import/export
|
|
164
|
-
-
|
|
164
|
+
- Detect unused keys
|
|
165
165
|
|
|
166
|
-
##
|
|
166
|
+
## Key Naming Conventions
|
|
167
167
|
|
|
168
|
-
|
|
|
169
|
-
|
|
170
|
-
| `common.*` |
|
|
171
|
-
| `error.*` |
|
|
172
|
-
| `validation.*` |
|
|
173
|
-
| `entity.*` | Entity
|
|
174
|
-
| `enum.*` | Enum
|
|
175
|
-
| `menu.*` |
|
|
176
|
-
| `rc.*` | react-components
|
|
168
|
+
| Pattern | Purpose | Example |
|
|
169
|
+
|---------|---------|---------|
|
|
170
|
+
| `common.*` | Common UI text | `common.save`, `common.cancel` |
|
|
171
|
+
| `error.*` | Error messages | `error.notFound`, `error.forbidden` |
|
|
172
|
+
| `validation.*` | Validation messages | `validation.required`, `validation.email` |
|
|
173
|
+
| `entity.*` | Entity-related | `entity.User`, `entity.User.email` |
|
|
174
|
+
| `enum.*` | Enum labels | `enum.UserRole.admin` |
|
|
175
|
+
| `menu.*` | Menu/navigation | `menu.home`, `menu.settings` |
|
|
176
|
+
| `rc.*` | react-components built-in | `rc.pagination.next` |
|
|
177
177
|
|
|
178
|
-
##
|
|
178
|
+
## Troubleshooting
|
|
179
179
|
|
|
180
|
-
### LocalizedString
|
|
180
|
+
### LocalizedString Type Error
|
|
181
181
|
|
|
182
|
-
`Type 'string' is not assignable to type '{ __brand: "LocalizedString"; }'
|
|
182
|
+
When you see `Type 'string' is not assignable to type '{ __brand: "LocalizedString"; }'`:
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
**Cause 1: Missing i18n key in ko.ts**
|
|
185
185
|
```typescript
|
|
186
|
-
//
|
|
187
|
-
SD("validation.password"); //
|
|
186
|
+
// Wrong: type error when key is missing from ko.ts
|
|
187
|
+
SD("validation.password"); // key not in ko.ts
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
Fix: Add the key to ko.ts and run `pnpm sync`
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
**Cause 2: Not using SD() in the model**
|
|
193
193
|
```typescript
|
|
194
|
-
//
|
|
194
|
+
// Wrong: using a raw string directly
|
|
195
195
|
throw new BadRequestException("이메일이 잘못되었습니다");
|
|
196
196
|
|
|
197
|
-
//
|
|
197
|
+
// Correct: use the SD() function
|
|
198
198
|
throw new BadRequestException(SD("error.invalidEmail"));
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
All user-facing strings must be wrapped with the SD() function.
|
|
202
202
|
|
|
203
203
|
---
|
|
204
204
|
|
|
205
|
-
##
|
|
205
|
+
## Type Safety
|
|
206
206
|
|
|
207
|
-
### String PK
|
|
207
|
+
### String PK Support
|
|
208
208
|
|
|
209
|
-
Entity
|
|
209
|
+
When an Entity uses a String PK, the i18n function types must reflect this.
|
|
210
210
|
|
|
211
|
-
**BAD: Number
|
|
211
|
+
**BAD: Number only**
|
|
212
212
|
```typescript
|
|
213
213
|
// ko.ts
|
|
214
214
|
notFound: (name: string, id: number) => `존재하지 않는 ${name} ID ${id}`,
|
|
215
215
|
|
|
216
|
-
//
|
|
217
|
-
// Account, Session, User, Verification
|
|
216
|
+
// Type error when used with String PK entities
|
|
217
|
+
// Account, Session, User, Verification, etc.
|
|
218
218
|
throw new NotFoundException(SD("notFound")(this.modelName, id));
|
|
219
219
|
// Error: Argument of type 'string' is not assignable to parameter of type 'number'
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
-
**GOOD:
|
|
222
|
+
**GOOD: Support both with a union type**
|
|
223
223
|
```typescript
|
|
224
224
|
// ko.ts
|
|
225
225
|
notFound: (name: string, id: number | string) => `존재하지 않는 ${name} ID ${id}`,
|
|
226
226
|
|
|
227
|
-
// Number PK
|
|
227
|
+
// Works for both Number PK and String PK
|
|
228
228
|
throw new NotFoundException(SD("notFound")(this.modelName, id));
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
- `notFound`: Entity
|
|
233
|
-
- `error.entityNotFound`:
|
|
231
|
+
**Applicable i18n keys:**
|
|
232
|
+
- `notFound`: Entity lookup failure
|
|
233
|
+
- `error.entityNotFound`: Specific entity lookup failure (deprecated, prefer `notFound`)
|
|
234
234
|
|
|
235
|
-
###
|
|
235
|
+
### Type Patterns by Namespace
|
|
236
236
|
|
|
237
|
-
| i18n
|
|
238
|
-
|
|
239
|
-
| `notFound` | `(name: string, id: number \| string)` | Entity
|
|
240
|
-
| `search.invalidField` | `(field: string)` |
|
|
241
|
-
| `validation.required` | `(field: string)` |
|
|
242
|
-
| `validation.maxLength` | `(field: string, max: number)` |
|
|
243
|
-
| `validation.minLength` | `(field: string, min: number)` |
|
|
244
|
-
| `entity.create` | `(name: string)` | Entity
|
|
245
|
-
| `entity.edit` | `(name: string, id: number \| string)` | Entity
|
|
237
|
+
| i18n key | Parameter type | Purpose |
|
|
238
|
+
|---------|----------------|---------|
|
|
239
|
+
| `notFound` | `(name: string, id: number \| string)` | Entity lookup failure |
|
|
240
|
+
| `search.invalidField` | `(field: string)` | Invalid search field |
|
|
241
|
+
| `validation.required` | `(field: string)` | Required field validation |
|
|
242
|
+
| `validation.maxLength` | `(field: string, max: number)` | Max length validation |
|
|
243
|
+
| `validation.minLength` | `(field: string, min: number)` | Min length validation |
|
|
244
|
+
| `entity.create` | `(name: string)` | Entity creation screen |
|
|
245
|
+
| `entity.edit` | `(name: string, id: number \| string)` | Entity edit screen |
|
|
246
246
|
|
|
247
|
-
###
|
|
247
|
+
### How to Check PK Type
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
Check the id field type in the Entity file:
|
|
250
250
|
|
|
251
251
|
```json
|
|
252
252
|
// String PK
|
|
@@ -256,7 +256,7 @@ Entity 파일에서 id 필드 타입을 확인:
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
// Number PK (
|
|
259
|
+
// Number PK (default)
|
|
260
260
|
{
|
|
261
261
|
"props": {
|
|
262
262
|
"id": { "type": "int", "autoIncrement": true }
|
|
@@ -264,7 +264,7 @@ Entity 파일에서 id 필드 타입을 확인:
|
|
|
264
264
|
}
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
**
|
|
267
|
+
**String PK entities in the KOPRI project:**
|
|
268
268
|
- Account
|
|
269
269
|
- Session
|
|
270
270
|
- User
|
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sonamu-migration
|
|
3
|
-
description: Sonamu
|
|
3
|
+
description: Sonamu database migration. CREATE/ALTER TABLE, FK ordering, up/down functions. Use when modifying database schema.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Migration
|
|
7
7
|
|
|
8
|
-
## CRITICAL:
|
|
8
|
+
## CRITICAL: Migrations are generated via Sonamu UI or CLI
|
|
9
9
|
|
|
10
|
-
**
|
|
10
|
+
**Do not write migration files manually or execute SQL directly.** Sonamu detects entity.json changes and generates accurate migration files.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
-
|
|
12
|
+
**Prerequisites:**
|
|
13
|
+
- `pnpm dev` must be running in `/packages/api`
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
1.
|
|
17
|
-
2.
|
|
18
|
-
3.
|
|
19
|
-
4.
|
|
15
|
+
**Method 1: Sonamu UI (confirm with user before choosing)**
|
|
16
|
+
1. Open Sonamu UI in browser: `http://localhost:34900/sonamu-ui`
|
|
17
|
+
2. Check the prepared list in the Migration menu
|
|
18
|
+
3. Generate a migration file with the "Generate" button
|
|
19
|
+
4. Apply to the actual DB with the "Apply" button
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
**Method 2: CLI**
|
|
22
22
|
```bash
|
|
23
23
|
cd packages/api
|
|
24
|
-
pnpm sonamu migrate generate # migration
|
|
25
|
-
pnpm sonamu migrate run #
|
|
24
|
+
pnpm sonamu migrate generate # Generate migration file
|
|
25
|
+
pnpm sonamu migrate run # Apply to actual DB
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
**CRITICAL:
|
|
28
|
+
**CRITICAL: Ask the user whether to proceed with UI or CLI before starting.**
|
|
29
29
|
|
|
30
30
|
**DO NOT:**
|
|
31
|
-
-
|
|
32
|
-
- `CREATE TABLE`, `ALTER TABLE
|
|
31
|
+
- Write migration files manually
|
|
32
|
+
- Execute SQL directly (`CREATE TABLE`, `ALTER TABLE`, etc.)
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
**Exception:** Only raw SQL is allowed for special cases that Sonamu cannot handle automatically, such as PK type changes (see "PK Type Change" section below)
|
|
35
35
|
|
|
36
|
-
**CRITICAL:
|
|
36
|
+
**CRITICAL: Handle cross-table cascading changes in a single file.** Cascading changes across multiple tables — such as FK drop → type change → FK restore — must be executed in order within a single migration file. Splitting into separate files causes constraint violations in intermediate states. (Details: see "PK Type Change" section below)
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Basic Structure
|
|
39
39
|
|
|
40
40
|
```typescript
|
|
41
41
|
import type { Knex } from "knex";
|
|
42
42
|
|
|
43
43
|
export async function up(knex: Knex): Promise<void> {
|
|
44
|
-
//
|
|
44
|
+
// Apply changes
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export async function down(knex: Knex): Promise<void> {
|
|
48
|
-
//
|
|
48
|
+
// Rollback changes
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -71,14 +71,14 @@ export async function down(knex: Knex): Promise<void> {
|
|
|
71
71
|
## ALTER TABLE
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
//
|
|
74
|
+
// Add column
|
|
75
75
|
export async function up(knex: Knex): Promise<void> {
|
|
76
76
|
await knex.schema.alterTable("users", (table) => {
|
|
77
77
|
table.string("phone", 20).nullable();
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
//
|
|
81
|
+
// Drop column
|
|
82
82
|
export async function down(knex: Knex): Promise<void> {
|
|
83
83
|
await knex.schema.alterTable("users", (table) => {
|
|
84
84
|
table.dropColumns("phone");
|
|
@@ -104,100 +104,100 @@ export async function down(knex: Knex): Promise<void> {
|
|
|
104
104
|
}
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
## PK
|
|
107
|
+
## PK Type Change
|
|
108
108
|
|
|
109
|
-
###
|
|
110
|
-
|
|
109
|
+
### Situation
|
|
110
|
+
When the PK type of an existing table must be changed (e.g. integer -> text, bigint -> uuid), with FKs referencing that PK in multiple tables.
|
|
111
111
|
|
|
112
|
-
###
|
|
112
|
+
### Required Order
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
When changing the type of a column that has referential integrity (a PK referenced by FKs), the following order must be followed:
|
|
115
115
|
|
|
116
116
|
```typescript
|
|
117
117
|
export async function up(knex: Knex): Promise<void> {
|
|
118
|
-
// 1
|
|
118
|
+
// Step 1: DROP all FK constraints referencing this PK
|
|
119
119
|
await knex.raw('ALTER TABLE "child_table_1" DROP CONSTRAINT "child_table_1_parent_id_foreign"');
|
|
120
120
|
await knex.raw('ALTER TABLE "child_table_2" DROP CONSTRAINT "child_table_2_parent_id_foreign"');
|
|
121
121
|
|
|
122
|
-
// 2
|
|
122
|
+
// Step 2: DROP the PK constraint
|
|
123
123
|
await knex.raw('ALTER TABLE "parent_table" DROP CONSTRAINT "parent_table_pkey"');
|
|
124
124
|
|
|
125
|
-
// 3
|
|
125
|
+
// Step 3: Change types of the PK column and all FK columns simultaneously
|
|
126
126
|
await knex.raw('ALTER TABLE "parent_table" ALTER COLUMN "id" TYPE new_type USING "id"::new_type');
|
|
127
127
|
await knex.raw('ALTER TABLE "child_table_1" ALTER COLUMN "parent_id" TYPE new_type USING "parent_id"::new_type');
|
|
128
128
|
await knex.raw('ALTER TABLE "child_table_2" ALTER COLUMN "parent_id" TYPE new_type USING "parent_id"::new_type');
|
|
129
129
|
|
|
130
|
-
// 4
|
|
130
|
+
// Step 4: ADD the PK constraint
|
|
131
131
|
await knex.raw('ALTER TABLE "parent_table" ADD CONSTRAINT "parent_table_pkey" PRIMARY KEY ("id")');
|
|
132
132
|
|
|
133
|
-
// 5
|
|
133
|
+
// Step 5: ADD all FK constraints
|
|
134
134
|
await knex.raw('ALTER TABLE "child_table_1" ADD CONSTRAINT "child_table_1_parent_id_foreign" FOREIGN KEY ("parent_id") REFERENCES "parent_table"("id") ON UPDATE RESTRICT ON DELETE CASCADE');
|
|
135
135
|
await knex.raw('ALTER TABLE "child_table_2" ADD CONSTRAINT "child_table_2_parent_id_foreign" FOREIGN KEY ("parent_id") REFERENCES "parent_table"("id") ON UPDATE RESTRICT ON DELETE RESTRICT');
|
|
136
136
|
}
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
###
|
|
139
|
+
### Core Principles
|
|
140
140
|
|
|
141
|
-
1. **
|
|
141
|
+
1. **Cannot change the type of a referenced column while FK constraints exist**: PostgreSQL throws an error if the types of the FK and referenced PK do not match. Constraints must be removed first.
|
|
142
142
|
|
|
143
|
-
2.
|
|
143
|
+
2. **Handle all changes in a single migration**: Splitting into multiple migrations causes constraint violations in intermediate states. Change the types of the PK and all FKs in one go.
|
|
144
144
|
|
|
145
|
-
3. **knex schema builder
|
|
145
|
+
3. **Prefer raw SQL over the knex schema builder**: Guarantees clear execution order, allows explicit constraint naming, and supports complex type conversions (USING).
|
|
146
146
|
|
|
147
|
-
###
|
|
147
|
+
### Real Example: users.id integer -> text
|
|
148
148
|
|
|
149
149
|
```typescript
|
|
150
150
|
export async function up(knex: Knex): Promise<void> {
|
|
151
|
-
// 1. FK
|
|
151
|
+
// 1. Remove FK constraints
|
|
152
152
|
await knex.raw('ALTER TABLE "accounts" DROP CONSTRAINT "accounts_user_id_foreign"');
|
|
153
153
|
await knex.raw('ALTER TABLE "sessions" DROP CONSTRAINT "sessions_user_id_foreign"');
|
|
154
154
|
|
|
155
|
-
// 2. PK
|
|
155
|
+
// 2. Remove PK constraint
|
|
156
156
|
await knex.raw('ALTER TABLE "users" DROP CONSTRAINT "users_pkey"');
|
|
157
157
|
|
|
158
|
-
// 3.
|
|
158
|
+
// 3. Change types (explicit conversion with USING clause)
|
|
159
159
|
await knex.raw('ALTER TABLE "users" ALTER COLUMN "id" TYPE text USING "id"::text');
|
|
160
160
|
await knex.raw('ALTER TABLE "accounts" ALTER COLUMN "user_id" TYPE text USING "user_id"::text');
|
|
161
161
|
await knex.raw('ALTER TABLE "sessions" ALTER COLUMN "user_id" TYPE text USING "user_id"::text');
|
|
162
162
|
|
|
163
|
-
// 4. PK
|
|
163
|
+
// 4. Restore PK constraint
|
|
164
164
|
await knex.raw('ALTER TABLE "users" ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id")');
|
|
165
165
|
|
|
166
|
-
// 5. FK
|
|
166
|
+
// 5. Restore FK constraints
|
|
167
167
|
await knex.raw('ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_foreign" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON UPDATE RESTRICT ON DELETE CASCADE');
|
|
168
168
|
await knex.raw('ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_foreign" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON UPDATE RESTRICT ON DELETE CASCADE');
|
|
169
169
|
}
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
-
### FK
|
|
172
|
+
### Finding FK Reference Tables
|
|
173
173
|
|
|
174
174
|
```bash
|
|
175
|
-
#
|
|
175
|
+
# Find relations referencing a specific entity in entity files
|
|
176
176
|
grep -r "with.*User" --include="*.entity.json"
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
###
|
|
179
|
+
### Common Mistakes
|
|
180
180
|
|
|
181
|
-
1.
|
|
181
|
+
1. **Splitting into multiple migrations**: Separating FK drop, type change, and FK restore into separate files causes a constraint violation immediately after the first file is applied. Always consolidate related changes into a single file.
|
|
182
182
|
|
|
183
|
-
2. **
|
|
183
|
+
2. **Changing type without removing constraint**: Causes `cannot alter type of a column used by a foreign key` error
|
|
184
184
|
|
|
185
|
-
3. **USING
|
|
185
|
+
3. **Missing USING clause**: Causes `column "id" cannot be cast automatically to type text` error. `USING "id"::text` is required for integer -> text.
|
|
186
186
|
|
|
187
|
-
4. **constraint
|
|
187
|
+
4. **Constraint name mismatch**: Verify the exact constraint name with `SELECT constraint_name FROM information_schema.table_constraints WHERE table_name = 'accounts'`.
|
|
188
188
|
|
|
189
|
-
##
|
|
189
|
+
## Commands
|
|
190
190
|
|
|
191
|
-
**`packages/api`**
|
|
191
|
+
Run from the **`packages/api`** directory:
|
|
192
192
|
|
|
193
193
|
```bash
|
|
194
194
|
cd packages/api
|
|
195
|
-
pnpm sonamu migrate run #
|
|
196
|
-
pnpm sonamu migrate status #
|
|
197
|
-
pnpm sonamu migrate apply #
|
|
195
|
+
pnpm sonamu migrate run # Run all pending migrations on local DB
|
|
196
|
+
pnpm sonamu migrate status # Check migration status
|
|
197
|
+
pnpm sonamu migrate apply # Apply to a specific DB configuration target
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
**Note**: `migrate up` and `migrate rollback` are not provided in the CLI. Rollback is available from Sonamu UI.
|
|
201
201
|
|
|
202
202
|
## Entity Type → DB Type
|
|
203
203
|
|
|
@@ -223,13 +223,13 @@ pnpm sonamu migrate apply # 특정 DB 설정 대상으로 적용
|
|
|
223
223
|
| `vector[]` | `vector(n)[]` | `table.specificType(name, 'vector(n)[]')` |
|
|
224
224
|
| `tsvector` | `tsvector` | `table.specificType(name, 'tsvector')` |
|
|
225
225
|
|
|
226
|
-
##
|
|
226
|
+
## Execution Order (Important!)
|
|
227
227
|
|
|
228
228
|
```
|
|
229
|
-
1. CREATE TABLE companies (
|
|
230
|
-
2. CREATE TABLE departments (company_id
|
|
231
|
-
3. CREATE TABLE users (
|
|
232
|
-
4. CREATE TABLE employees (user_id, department_id
|
|
229
|
+
1. CREATE TABLE companies (no dependencies)
|
|
230
|
+
2. CREATE TABLE departments (company_id column only)
|
|
231
|
+
3. CREATE TABLE users (no dependencies)
|
|
232
|
+
4. CREATE TABLE employees (user_id, department_id columns only)
|
|
233
233
|
5. FOREIGN KEY departments (company_id → companies.id)
|
|
234
234
|
6. FOREIGN KEY employees (user_id → users.id, etc.)
|
|
235
235
|
```
|
|
@@ -244,50 +244,50 @@ pnpm sonamu migrate apply # 특정 DB 설정 대상으로 적용
|
|
|
244
244
|
|
|
245
245
|
### 1. MUST Build dist First
|
|
246
246
|
|
|
247
|
-
sync
|
|
247
|
+
The sync command references `dist/sonamu.config.js`. An error will occur if it does not exist.
|
|
248
248
|
|
|
249
249
|
```bash
|
|
250
250
|
cd packages/api
|
|
251
251
|
npx swc src/sonamu.config.ts -o dist/sonamu.config.js --config-file .swcrc
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
-
### 2. DB
|
|
254
|
+
### 2. Verify DB Connection
|
|
255
255
|
|
|
256
256
|
```bash
|
|
257
|
-
# Docker
|
|
257
|
+
# Check Docker containers
|
|
258
258
|
docker ps | grep container
|
|
259
259
|
|
|
260
|
-
# sonamu.config.ts
|
|
260
|
+
# Check DB configuration in sonamu.config.ts
|
|
261
261
|
# - host, port, database, user, password
|
|
262
|
-
# - PostgreSQL vs MySQL
|
|
262
|
+
# - Distinguish PostgreSQL vs MySQL
|
|
263
263
|
```
|
|
264
264
|
|
|
265
|
-
### 3.
|
|
265
|
+
### 3. Circular References
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
Split the migration, or set the FK to nullable and add it later.
|
|
268
268
|
|
|
269
|
-
##
|
|
269
|
+
## Full Workflow
|
|
270
270
|
|
|
271
271
|
```bash
|
|
272
272
|
cd packages/api
|
|
273
273
|
|
|
274
|
-
# 1. config
|
|
274
|
+
# 1. Build config
|
|
275
275
|
npx swc src/sonamu.config.ts -o dist/sonamu.config.js --config-file .swcrc
|
|
276
276
|
|
|
277
|
-
# 2. sync
|
|
277
|
+
# 2. Run sync
|
|
278
278
|
pnpm sonamu sync
|
|
279
279
|
|
|
280
|
-
# 3. Sonamu UI
|
|
280
|
+
# 3. Generate/run migration from Sonamu UI
|
|
281
281
|
|
|
282
|
-
# 4.
|
|
282
|
+
# 4. Run scaffolding
|
|
283
283
|
|
|
284
|
-
# 5.
|
|
284
|
+
# 5. Check/add orderBy cases in model (see model.md)
|
|
285
285
|
|
|
286
|
-
# 6. API
|
|
286
|
+
# 6. Build API
|
|
287
287
|
npm run build
|
|
288
288
|
|
|
289
|
-
# 7.
|
|
289
|
+
# 7. Check/fix web form (see frontend.md)
|
|
290
290
|
|
|
291
|
-
# 8.
|
|
291
|
+
# 8. Build web
|
|
292
292
|
cd ../web && npm run build
|
|
293
293
|
```
|