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.
Files changed (88) hide show
  1. package/dist/api/__tests__/config.test.js +189 -0
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +7 -2
  4. package/dist/api/sonamu.d.ts.map +1 -1
  5. package/dist/api/sonamu.js +14 -10
  6. package/dist/auth/index.d.ts +1 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +2 -1
  9. package/dist/auth/knex-adapter.d.ts +23 -0
  10. package/dist/auth/knex-adapter.d.ts.map +1 -0
  11. package/dist/auth/knex-adapter.js +163 -0
  12. package/dist/auth/plugins/wrappers/admin.d.ts +2 -2
  13. package/dist/bin/__tests__/ts-loader-register.test.js +45 -0
  14. package/dist/bin/cli.js +47 -9
  15. package/dist/bin/ts-loader-register.js +3 -29
  16. package/dist/bin/ts-loader-registration.d.ts +2 -0
  17. package/dist/bin/ts-loader-registration.d.ts.map +1 -0
  18. package/dist/bin/ts-loader-registration.js +42 -0
  19. package/dist/cone/cone-generator.js +3 -3
  20. package/dist/database/puri-subset.test-d.js +9 -1
  21. package/dist/database/puri-subset.types.d.ts +1 -1
  22. package/dist/database/puri-subset.types.d.ts.map +1 -1
  23. package/dist/database/puri-subset.types.js +1 -1
  24. package/dist/testing/fixture-generator.js +5 -5
  25. package/dist/ui/ai-client.js +2 -2
  26. package/dist/ui/api.d.ts.map +1 -1
  27. package/dist/ui/api.js +14 -14
  28. package/dist/ui/cdd-service.d.ts +15 -18
  29. package/dist/ui/cdd-service.d.ts.map +1 -1
  30. package/dist/ui/cdd-service.js +246 -222
  31. package/dist/ui/cdd-types.d.ts +41 -68
  32. package/dist/ui/cdd-types.d.ts.map +1 -1
  33. package/dist/ui/cdd-types.js +2 -2
  34. package/dist/ui-web/assets/index-CKo0Z2Iu.css +1 -0
  35. package/dist/ui-web/assets/{index-CxiydzeC.js → index-DK-2aacv.js} +83 -83
  36. package/dist/ui-web/index.html +2 -2
  37. package/package.json +6 -2
  38. package/src/api/__tests__/config.test.ts +225 -0
  39. package/src/api/config.ts +10 -4
  40. package/src/api/sonamu.ts +16 -13
  41. package/src/auth/index.ts +1 -0
  42. package/src/auth/knex-adapter.ts +208 -0
  43. package/src/bin/__tests__/ts-loader-register.test.ts +62 -0
  44. package/src/bin/cli.ts +52 -9
  45. package/src/bin/ts-loader-register.ts +2 -32
  46. package/src/bin/ts-loader-registration.ts +55 -0
  47. package/src/cone/cone-generator.ts +2 -2
  48. package/src/database/puri-subset.test-d.ts +102 -0
  49. package/src/database/puri-subset.types.ts +1 -1
  50. package/src/skills/commands/sonamu-skills.md +20 -0
  51. package/src/skills/sonamu/SKILL.md +179 -137
  52. package/src/skills/sonamu/ai-agents.md +69 -69
  53. package/src/skills/sonamu/api.md +147 -147
  54. package/src/skills/sonamu/auth-migration.md +220 -220
  55. package/src/skills/sonamu/auth-plugins.md +83 -83
  56. package/src/skills/sonamu/auth.md +106 -106
  57. package/src/skills/sonamu/cdd.md +65 -200
  58. package/src/skills/sonamu/cone.md +138 -138
  59. package/src/skills/sonamu/config.md +191 -191
  60. package/src/skills/sonamu/create-sonamu.md +66 -66
  61. package/src/skills/sonamu/database.md +158 -158
  62. package/src/skills/sonamu/entity-basic.md +292 -293
  63. package/src/skills/sonamu/entity-relations.md +246 -246
  64. package/src/skills/sonamu/entity-validation-checklist.md +124 -124
  65. package/src/skills/sonamu/fixture-cli.md +231 -231
  66. package/src/skills/sonamu/framework-change.md +37 -37
  67. package/src/skills/sonamu/frontend.md +223 -223
  68. package/src/skills/sonamu/i18n.md +82 -82
  69. package/src/skills/sonamu/migration.md +77 -77
  70. package/src/skills/sonamu/model.md +222 -222
  71. package/src/skills/sonamu/naite.md +86 -86
  72. package/src/skills/sonamu/project-init.md +228 -228
  73. package/src/skills/sonamu/puri.md +122 -122
  74. package/src/skills/sonamu/scaffolding.md +154 -154
  75. package/src/skills/sonamu/skill-contribution.md +124 -124
  76. package/src/skills/sonamu/subset.md +46 -46
  77. package/src/skills/sonamu/tasks.md +82 -82
  78. package/src/skills/sonamu/testing-devrunner.md +147 -147
  79. package/src/skills/sonamu/testing.md +673 -673
  80. package/src/skills/sonamu/upsert.md +79 -79
  81. package/src/skills/sonamu/vector.md +67 -67
  82. package/src/testing/fixture-generator.ts +4 -4
  83. package/src/ui/ai-client.ts +1 -1
  84. package/src/ui/api.ts +18 -17
  85. package/src/ui/cdd-service.ts +264 -254
  86. package/src/ui/cdd-types.ts +40 -75
  87. package/dist/ui-web/assets/index-BrQKU3j9.css +0 -1
  88. package/src/skills/sonamu/workflow.md +0 -317
@@ -1,24 +1,24 @@
1
1
  ---
2
2
  name: sonamu-auth-migration
3
- description: better-auth 등 외부 인증 통합 시 User.id 타입 변경 (integer→string). Entity, Migration, SaveParams, test-helpers 전체 프로세스. PK 타입 변경 후 플러그인 Entity/Migration 작성 패턴. Use when migrating User.id from integer to string PK, or writing plugin Entity/Migration after PK type change.
3
+ description: Changing User.id type (integer→string) when integrating external auth such as better-auth. Full process covering Entity, Migration, SaveParams, test-helpers. Patterns for writing plugin Entity/Migration after PK type change. Use when migrating User.id from integer to string PK, or writing plugin Entity/Migration after PK type change.
4
4
  ---
5
5
 
6
- # Auth 시스템 Migration (better-auth 외부 인증 통합)
6
+ # Auth System Migration (Integrating External Auth such as better-auth)
7
7
 
8
8
  ## Situation
9
9
 
10
- 외부 인증 시스템(better-auth, NextAuth ) 기존 Sonamu 프로젝트에 통합할 때 User.id 타입 변경이 필요한 경우
10
+ When User.id type change is needed to integrate an external authentication system (better-auth, NextAuth, etc.) with an existing Sonamu project
11
11
 
12
12
  ## Problem
13
13
 
14
- - better-auth User.id string(text) 타입으로 요구
15
- - 기존 시스템은 integer 타입 사용
16
- - User를 참조하는 모든 FK도 함께 변경 필요
17
- - Migration 순서 실수 FK constraint 위반
14
+ - better-auth requires User.id to be of type string (text)
15
+ - The existing system uses integer type
16
+ - All FKs referencing User must be changed together
17
+ - FK constraint violations occur if migration order is wrong
18
18
 
19
19
  ## Solution
20
20
 
21
- ### 1. Entity 타입 변경
21
+ ### 1. Change Entity Type
22
22
 
23
23
  ```json
24
24
  // user.entity.json
@@ -27,39 +27,39 @@ description: better-auth 등 외부 인증 통합 시 User.id 타입 변경 (int
27
27
  }
28
28
  ```
29
29
 
30
- 주의: integer에서 string으로 변경
30
+ Note: changing from integer to string
31
31
 
32
- ### 2. 영향받는 FK 확인
32
+ ### 2. Identify Affected FKs
33
33
 
34
34
  ```bash
35
- # User를 참조하는 모든 relation 찾기
35
+ # Find all relations referencing User
36
36
  grep -r "with.*User" --include="*.entity.json"
37
37
  ```
38
38
 
39
- 일반적으로 영향받는 테이블:
39
+ Tables commonly affected:
40
40
 
41
41
  - accounts.user_id
42
42
  - sessions.user_id
43
- - evaluation_committees.evaluator_id (또는 다른 User 참조 FK)
43
+ - evaluation_committees.evaluator_id (or other User-referencing FKs)
44
44
  - project_participants.user_id
45
45
  - reports.submitted_by_id
46
46
 
47
- ### 3. Migration 작성 순서 (필수)
47
+ ### 3. Migration Write Order (Required)
48
48
 
49
- 잘못된 순서 - FK constraint 위반:
49
+ Wrong order - FK constraint violation:
50
50
 
51
51
  ```typescript
52
- // 잘못된
52
+ // wrong example
53
53
  await knex.schema.alterTable("accounts", (table) => {
54
- table.text("user_id").alter(); // 실패: FK 아직 users.id(integer)를 참조 중
54
+ table.text("user_id").alter(); // fails: FK still references users.id(integer)
55
55
  });
56
56
  ```
57
57
 
58
- 올바른 순서:
58
+ Correct order:
59
59
 
60
60
  ```typescript
61
61
  export async function up(knex: Knex): Promise<void> {
62
- // 1단계: 모든 FK 제약조건 제거
62
+ // Step 1: Remove all FK constraints
63
63
  await knex.raw(
64
64
  'ALTER TABLE "accounts" DROP CONSTRAINT "accounts_user_id_foreign"',
65
65
  );
@@ -76,10 +76,10 @@ export async function up(knex: Knex): Promise<void> {
76
76
  'ALTER TABLE "reports" DROP CONSTRAINT "reports_submitted_by_id_foreign"',
77
77
  );
78
78
 
79
- // 2단계: PK 제약조건 제거
79
+ // Step 2: Remove PK constraint
80
80
  await knex.raw('ALTER TABLE "users" DROP CONSTRAINT "users_pkey"');
81
81
 
82
- // 3단계: 모든 컬럼 타입 변경 (부모 PK + 자식 FK)
82
+ // Step 3: Change all column types (parent PK + child FKs)
83
83
  await knex.raw(
84
84
  'ALTER TABLE "users" ALTER COLUMN "id" TYPE text USING "id"::text',
85
85
  );
@@ -99,12 +99,12 @@ export async function up(knex: Knex): Promise<void> {
99
99
  'ALTER TABLE "reports" ALTER COLUMN "submitted_by_id" TYPE text USING "submitted_by_id"::text',
100
100
  );
101
101
 
102
- // 4단계: PK 제약조건 복구
102
+ // Step 4: Restore PK constraint
103
103
  await knex.raw(
104
104
  'ALTER TABLE "users" ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id")',
105
105
  );
106
106
 
107
- // 5단계: FK 제약조건 복구
107
+ // Step 5: Restore FK constraints
108
108
  await knex.raw(
109
109
  'ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_foreign" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON UPDATE RESTRICT ON DELETE CASCADE',
110
110
  );
@@ -123,32 +123,32 @@ export async function up(knex: Knex): Promise<void> {
123
123
  }
124
124
  ```
125
125
 
126
- 핵심 원칙:
126
+ Core principles:
127
127
 
128
- 1. FK constraint가 존재하는 상태에서는 참조 컬럼 타입 변경 불가
129
- 2. 모든 FK 제거 타입 변경, 다음 FK 복구
130
- 3. 하나의 migration에서 모든 변경을 처리
128
+ 1. Column type cannot be changed while FK constraints exist
129
+ 2. Remove all FKs, then change types, then restore FKs
130
+ 3. Handle all changes in a single migration
131
131
 
132
- ### 4. Migration 재생성 주의사항
132
+ ### 4. Notes When Regenerating Migrations
133
133
 
134
- Entity 변경 후 `pnpm generate` 실행 중복 migration이 생성됨:
134
+ Running `pnpm generate` after entity changes creates duplicate migrations:
135
135
 
136
136
  ```
137
- 20260203154926_alter_accounts_alter5.ts (accounts.user_id 변경)
138
- 20260203154927_alter_evaluation_committees.ts (evaluator_id 변경)
139
- 20260203154928_alter_project_participants.ts (user_id 변경)
140
- 20260203154929_alter_reports.ts (submitted_by_id 변경)
141
- 20260203154930_alter_sessions.ts (user_id 변경)
142
- 20260203154931_alter_users_pk_type.ts (통합: 모든 타입 변경)
137
+ 20260203154926_alter_accounts_alter5.ts (changes accounts.user_id only)
138
+ 20260203154927_alter_evaluation_committees.ts (changes evaluator_id only)
139
+ 20260203154928_alter_project_participants.ts (changes user_id only)
140
+ 20260203154929_alter_reports.ts (changes submitted_by_id only)
141
+ 20260203154930_alter_sessions.ts (changes user_id only)
142
+ 20260203154931_alter_users_pk_type.ts (consolidated: changes all types)
143
143
  ```
144
144
 
145
- 문제점:
145
+ Problems:
146
146
 
147
- - 개별 migration들(154926-154930) FK 타입만 변경 시도
148
- - 통합 migration(154931) 같은 컬럼들을 변경
149
- - 순서대로 실행하면 154926 먼저 실행되어 FK constraint 위반
147
+ - Individual migrations (154926-154930) attempt to change only FK types
148
+ - The consolidated migration (154931) changes the same columns
149
+ - Running in order causes 154926 to execute first and violate FK constraints
150
150
 
151
- 해결 방법 1: 개별 migration 삭제
151
+ Fix 1: Delete individual migrations
152
152
 
153
153
  ```bash
154
154
  rm 20260203154926_alter_accounts_alter5.ts
@@ -156,56 +156,56 @@ rm 20260203154927_alter_evaluation_committees.ts
156
156
  rm 20260203154928_alter_project_participants.ts
157
157
  rm 20260203154929_alter_reports.ts
158
158
  rm 20260203154930_alter_sessions.ts
159
- # 20260203154931_alter_users_pk_type.ts만 유지
159
+ # Keep only 20260203154931_alter_users_pk_type.ts
160
160
  ```
161
161
 
162
- 해결 방법 2: 개별 migration에서 user_id 관련 변경 제거
162
+ Fix 2: Remove user_id-related changes from individual migrations
163
163
 
164
- - accounts, sessions migration에 updated_at 변경만 남기고 user_id 변경 제거
165
- - evaluation_committees, project_participants, reports migration 삭제
166
- - 통합 migration에서만 타입 변경 수행
164
+ - Leave only updated_at changes in accounts, sessions migrations and remove user_id changes
165
+ - Delete evaluation_committees, project_participants, reports migrations
166
+ - Perform type changes in the consolidated migration only
167
167
 
168
- ### 5. SaveParams 타입 정의
168
+ ### 5. SaveParams Type Definitions
169
169
 
170
- Auth 관련 엔티티는 nullable 필드가 많으므로 SaveParams에서 모두 optional 처리 필요:
170
+ Auth-related entities have many nullable fields, so all must be treated as optional in SaveParams:
171
171
 
172
172
  ```typescript
173
173
  // account.types.ts
174
174
  export const AccountSaveParams = AccountBaseSchema.partial({
175
- id: true, // create update 구분을 위해
176
- created_at: true, // dbDefault로 자동 생성
177
- updated_at: true, // dbDefault로 자동 생성
178
- access_token: true, // nullable - OAuth 전용
179
- refresh_token: true, // nullable - OAuth 전용
180
- id_token: true, // nullable - OAuth 전용
175
+ id: true, // to distinguish create vs update
176
+ created_at: true, // auto-generated via dbDefault
177
+ updated_at: true, // auto-generated via dbDefault
178
+ access_token: true, // nullable - OAuth only
179
+ refresh_token: true, // nullable - OAuth only
180
+ id_token: true, // nullable - OAuth only
181
181
  access_token_expires_at: true, // nullable
182
182
  refresh_token_expires_at: true, // nullable
183
- scope: true, // nullable - OAuth 전용
184
- password: true, // nullable - credential 전용
183
+ scope: true, // nullable - OAuth only
184
+ password: true, // nullable - credential only
185
185
  });
186
186
  ```
187
187
 
188
- 원칙:
188
+ Principles:
189
189
 
190
- - id: optional (create 생성, update 시 필수)
191
- - created_at, updated_at: optional (dbDefault로 자동 생성)
192
- - entity에서 nullable: true 모든 필드: optional
190
+ - id: optional (generated on create, required on update)
191
+ - created_at, updated_at: optional (auto-generated via dbDefault)
192
+ - All fields with nullable: true in entity: optional
193
193
 
194
- 이렇게 하지 않으면 테스트 작성 타입 에러 발생:
194
+ Not doing this causes type errors when writing tests:
195
195
 
196
196
  ```typescript
197
- // SaveParams에서 password optional 아니면
197
+ // If password is not optional in SaveParams
198
198
  await AccountModel.save([
199
199
  {
200
200
  provider_id: "google",
201
- // password 필드를 제공하지 않으면 타입 에러 발생
201
+ // type error if password field is not provided
202
202
  },
203
203
  ]);
204
204
  ```
205
205
 
206
- ### 6. 테스트 작성 패턴
206
+ ### 6. Test Writing Patterns
207
207
 
208
- 나쁜 - OAuth 계정에 불필요한 필드 제공:
208
+ Bad example - providing unnecessary fields for OAuth account:
209
209
 
210
210
  ```typescript
211
211
  await AccountModel.save([
@@ -217,7 +217,7 @@ await AccountModel.save([
217
217
  access_token: "token_123",
218
218
  refresh_token: "refresh_123",
219
219
  id_token: "id_token_123",
220
- password: "hashed_password", // OAuth에는 불필요
220
+ password: "hashed_password", // unnecessary for OAuth
221
221
  scope: "openid profile email",
222
222
  access_token_expires_at: new Date(),
223
223
  refresh_token_expires_at: new Date(),
@@ -227,10 +227,10 @@ await AccountModel.save([
227
227
  ]);
228
228
  ```
229
229
 
230
- 좋은 - 필수 필드와 의미있는 필드만 제공:
230
+ Good example - provide only required and meaningful fields:
231
231
 
232
232
  ```typescript
233
- // OAuth 계정
233
+ // OAuth account
234
234
  await AccountModel.save([
235
235
  {
236
236
  id: `acc_${Date.now()}`,
@@ -241,7 +241,7 @@ await AccountModel.save([
241
241
  },
242
242
  ]);
243
243
 
244
- // Credential 계정
244
+ // Credential account
245
245
  await AccountModel.save([
246
246
  {
247
247
  id: `acc_${Date.now()}`,
@@ -253,50 +253,50 @@ await AccountModel.save([
253
253
  ]);
254
254
  ```
255
255
 
256
- 원칙:
256
+ Principles:
257
257
 
258
- - provider 타입에 맞는 필드만 제공
259
- - nullable 필드는 테스트에 필요한 경우에만 제공
260
- - dbDefault 필드(created_at, updated_at)는 제공하지 않음
258
+ - Provide only fields appropriate to each provider type
259
+ - Provide nullable fields only when needed for the test
260
+ - Do not provide dbDefault fields (created_at, updated_at)
261
261
 
262
- ### 7. test-helpers 타입 수정
262
+ ### 7. Updating test-helpers Types
263
263
 
264
- User.id string으로 변경되면 모든 헬퍼 함수의 타입도 수정 필요:
264
+ When User.id changes to string, all helper function types must be updated too:
265
265
 
266
- 잘못된 타입:
266
+ Wrong types:
267
267
 
268
268
  ```typescript
269
269
  export async function createTestUser(): Promise<Number> { ... }
270
270
  export async function createTestProjectParticipant(
271
271
  projectId: number,
272
- userId: number, // 잘못됨
272
+ userId: number, // wrong
273
273
  ): Promise<number> { ... }
274
274
  ```
275
275
 
276
- 올바른 타입:
276
+ Correct types:
277
277
 
278
278
  ```typescript
279
279
  export async function createTestUser(): Promise<string> { ... }
280
280
  export async function createTestProjectParticipant(
281
281
  projectId: number,
282
- userId: string, // 수정
282
+ userId: string, // fixed
283
283
  ): Promise<number> { ... }
284
284
  ```
285
285
 
286
- 확인 방법:
286
+ How to check:
287
287
 
288
288
  ```bash
289
- # test-helpers에서 user 관련 파라미터 찾기
289
+ # Find user-related parameters in test-helpers
290
290
  grep -n "userId.*number" src/testing/test-helpers.ts
291
291
  grep -n "evaluatorId.*number" src/testing/test-helpers.ts
292
292
  grep -n "submittedById.*number" src/testing/test-helpers.ts
293
293
  ```
294
294
 
295
- ### 8. HasMany 관계의 joinColumn 처리
295
+ ### 8. Handling joinColumn in HasMany Relationships
296
296
 
297
- HasMany 관계 설정 joinColumn 지정한 컬럼이 자식 엔티티에 존재해야 함:
297
+ When configuring a HasMany relationship, the column specified in joinColumn must exist in the child entity:
298
298
 
299
- 잘못된 설정:
299
+ Wrong configuration:
300
300
 
301
301
  ```json
302
302
  // project.entity.json
@@ -307,7 +307,7 @@ HasMany 관계 설정 시 joinColumn에 지정한 컬럼이 자식 엔티티에
307
307
  ]
308
308
  }
309
309
 
310
- // file.entity.json - entity_id 컬럼 없음
310
+ // file.entity.json - no entity_id column
311
311
  {
312
312
  "props": [
313
313
  { "name": "id", "type": "integer" },
@@ -316,39 +316,39 @@ HasMany 관계 설정 시 joinColumn에 지정한 컬럼이 자식 엔티티에
316
316
  }
317
317
  ```
318
318
 
319
- 에러 발생:
319
+ Error:
320
320
 
321
321
  ```
322
322
  column files.entity_id does not exist
323
323
  ```
324
324
 
325
- 올바른 설정:
325
+ Correct configuration:
326
326
 
327
327
  ```json
328
- // file.entity.json - entity_id 컬럼 추가
328
+ // file.entity.json - add entity_id column
329
329
  {
330
330
  "props": [
331
331
  { "name": "id", "type": "integer" },
332
- { "name": "entity_id", "type": "integer", "desc": "엔티티 ID" },
332
+ { "name": "entity_id", "type": "integer", "desc": "entity ID" },
333
333
  { "name": "url", "type": "string" }
334
334
  ]
335
335
  }
336
336
  ```
337
337
 
338
- 주의사항:
338
+ Notes:
339
339
 
340
- - joinColumn 자식 테이블의 실제 컬럼명
341
- - 자식 엔티티에 해당 컬럼이 반드시 존재해야
342
- - subset에도 포함시켜야 조회 가능
340
+ - joinColumn is the actual column name in the child table
341
+ - That column must exist in the child entity
342
+ - Must also be included in subsets to be queryable
343
343
 
344
- ### 9. better-auth 플러그인 통합
344
+ ### 9. Integrating better-auth Plugins
345
345
 
346
- #### PluginSchema 타입 매핑
346
+ #### PluginSchema Type Mapping
347
347
 
348
- better-auth 플러그인은 PluginSchema 타입으로 스키마를 정의합니다. camelCase 필드명이 자동으로 snake_case DB 컬럼명으로 매핑됩니다:
348
+ better-auth plugins define schemas with the PluginSchema type. camelCase field names are automatically mapped to snake_case DB column names:
349
349
 
350
350
  ```typescript
351
- // better-auth 플러그인 스키마 예시
351
+ // better-auth plugin schema example
352
352
  const schema = {
353
353
  user: {
354
354
  fields: {
@@ -361,10 +361,10 @@ const schema = {
361
361
  },
362
362
  };
363
363
 
364
- // DB에는 phone_number 저장됨 (snake_case)
364
+ // stored as phone_number in DB (snake_case)
365
365
  ```
366
366
 
367
- Sonamu Entity에서는 DB 컬럼명(snake_case) 그대로 사용:
367
+ In Sonamu Entity, use the DB column name (snake_case) as-is:
368
368
 
369
369
  ```json
370
370
  // user.entity.json
@@ -374,49 +374,49 @@ Sonamu Entity에서는 DB 컬럼명(snake_case)을 그대로 사용:
374
374
  "name": "phone_number",
375
375
  "type": "string",
376
376
  "nullable": true,
377
- "desc": "전화번호"
377
+ "desc": "phone number"
378
378
  }
379
379
  ]
380
380
  }
381
381
  ```
382
382
 
383
- #### 플러그인 카테고리
383
+ #### Plugin Categories
384
384
 
385
- | 카테고리 | 플러그인 | 영향 |
385
+ | Category | Plugins | Impact |
386
386
  | ------------ | ----------------------------------------------------------- | ---------------------------------------- |
387
- | 기본 인증 | email/password, OAuth, magic link, email OTP, multi-session | User/Session/Account/Verification 테이블 |
388
- | 사용자 확장 | username, phone number, admin, anonymous | User 테이블 필드 추가 |
389
- | 보안 | two-factor, passkey | 테이블 필요 (TwoFactor, Passkey) |
390
- | 엔터프라이즈 | organization, API key, SSO, JWT | 테이블 필요 (Organization, Member ) |
387
+ | Basic auth | email/password, OAuth, magic link, email OTP, multi-session | User/Session/Account/Verification tables |
388
+ | User extension | username, phone number, admin, anonymous | Adds fields to User table |
389
+ | Security | two-factor, passkey | New tables needed (TwoFactor, Passkey) |
390
+ | Enterprise | organization, API key, SSO, JWT | New tables needed (Organization, Member, etc.) |
391
391
 
392
- #### 스키마 요구사항별 분류
392
+ #### Classification by Schema Requirements
393
393
 
394
- **기존 테이블 확장만 필요 (User/Session에 필드 추가)**: username, phone number, admin, anonymous, multi-session
394
+ **Only existing table extension needed (add fields to User/Session)**: username, phone number, admin, anonymous, multi-session
395
395
 
396
- **새 테이블 필요**: OAuth(Account), magic link/email OTP(Verification), two-factor(TwoFactor), passkey(Passkey), organization(Organization, Member, Invitation), API key(APIKey), SSO(SAMLProvider, SAMLConnection)
396
+ **New tables needed**: OAuth(Account), magic link/email OTP(Verification), two-factor(TwoFactor), passkey(Passkey), organization(Organization, Member, Invitation), API key(APIKey), SSO(SAMLProvider, SAMLConnection)
397
397
 
398
- #### Entity 작성 패턴
398
+ #### Entity Writing Patterns
399
399
 
400
- **기존 테이블 확장** — User entity.json에 플러그인 필드 추가:
400
+ **Extending existing tables**add plugin fields to User entity.json:
401
401
 
402
402
  ```json
403
- // user.entity.json - 플러그인별 추가 필드 예시
403
+ // user.entity.json - example additional fields by plugin
404
404
  // phone-number: phone_number(nullable), phone_number_verified(boolean, dbDefault:"false")
405
405
  // admin: role(enum, dbDefault:"'user'"), banned(nullable), ban_reason(nullable), ban_expires(nullable)
406
406
  // username: username(string)
407
407
  // anonymous: is_anonymous(boolean, dbDefault:"false")
408
408
  ```
409
409
 
410
- **새 테이블 생성** — Account/TwoFactor 등: `pnpm sonamu stub entity`로 생성 플러그인 스키마에 맞는 필드 추가. 주요 주의사항:
410
+ **Creating new tables**For Account/TwoFactor etc.: generate with `pnpm sonamu stub entity` then add fields matching the plugin schema. Key notes:
411
411
 
412
- - `id`는 `string` 타입 (32 alphanumeric)
413
- - FK(`user_id`) `string` 타입 (User.id string이므로)
414
- - `nullable` 필드는 반드시 `"nullable": true` 명시
415
- - better-auth camelCase → Sonamu snake_case 사용
412
+ - `id` is `string` type (32-character alphanumeric)
413
+ - FK (`user_id`) is also `string` type (since User.id is string)
414
+ - `nullable` fields must explicitly have `"nullable": true`
415
+ - better-auth uses camelCase → Sonamu uses snake_case
416
416
 
417
- #### Migration 패턴
417
+ #### Migration Patterns
418
418
 
419
- **기존 테이블 필드 추가**`alterTable`로 플러그인 필드 추가:
419
+ **Adding fields to existing tables** add plugin fields with `alterTable`:
420
420
 
421
421
  ```typescript
422
422
  await knex.schema.alterTable("users", (table) => {
@@ -426,198 +426,198 @@ await knex.schema.alterTable("users", (table) => {
426
426
  });
427
427
  ```
428
428
 
429
- **새 테이블 생성** — FK 없이 먼저 생성, 이후 FK 추가 (분리 필수!):
429
+ **Creating new tables**create without FK first, then add FK (must be separated!):
430
430
 
431
431
  ```typescript
432
- // 1단계: 테이블 생성 (FK 없이)
432
+ // Step 1: create table (without FK)
433
433
  await knex.schema.createTable("two_factors", (table) => {
434
434
  table.text("id").primary();
435
- table.text("user_id").notNullable(); // FK 컬럼만, foreign() 없이
435
+ table.text("user_id").notNullable(); // FK column only, no foreign()
436
436
  table.text("secret").notNullable();
437
437
  });
438
- // 2단계: FK 추가
438
+ // Step 2: add FK
439
439
  await knex.schema.alterTable("two_factors", (table) => {
440
440
  table.foreign("user_id").references("users.id");
441
441
  });
442
442
  ```
443
443
 
444
- #### Sonamu 구현 예시
444
+ #### Sonamu Implementation Examples
445
445
 
446
- 현재 Sonamu에서 구현된 플러그인:
446
+ Currently implemented plugins in Sonamu:
447
447
 
448
- - phone-number 플러그인: User.phone_number, User.phone_number_verified
449
- - two-factor 플러그인: TwoFactor 테이블 (id, secret, backup_codes, user_id)
448
+ - phone-number plugin: User.phone_number, User.phone_number_verified
449
+ - two-factor plugin: TwoFactor table (id, secret, backup_codes, user_id)
450
450
 
451
- 참조 경로:
451
+ Reference paths:
452
452
 
453
- - 예제 프로젝트: `sonamu/examples/miomock/`
453
+ - Example project: `sonamu/examples/miomock/`
454
454
  - User Entity: `examples/miomock/api/src/application/user/user.entity.json`
455
455
  - TwoFactor Entity: `examples/miomock/api/src/application/two_factor/two_factor.entity.json`
456
456
 
457
- #### 플러그인 추가 순서
457
+ #### Plugin Addition Steps
458
458
 
459
- 1. Entity 작성: `{entity}.entity.json`에 필드 정의
460
- 2. Migration 생성: Sonamu UI에서 자동 생성 또는 수동 작성
461
- 3. SaveParams 수정: nullable 필드는 모두 partial 처리
462
- 4. Model 작성: 비즈니스 로직 구현
463
- 5. test-helpers 수정: userId 타입이 변경된 파라미터 수정
464
- 6. 테스트 작성: provider/플러그인별 테스트 케이스 작성
459
+ 1. Write Entity: define fields in `{entity}.entity.json`
460
+ 2. Generate Migration: auto-generate from Sonamu UI or write manually
461
+ 3. Update SaveParams: make all nullable fields partial
462
+ 4. Write Model: implement business logic
463
+ 5. Update test-helpers: fix parameters whose types changed, such as userId
464
+ 6. Write tests: write test cases for each provider/plugin
465
465
 
466
- #### 플러그인별 주의사항
466
+ #### Plugin-Specific Notes
467
467
 
468
- - **OAuth**: provider별로 다른 필드 사용. SaveParams에서 access_token/refresh_token/password 모두 optional
469
- - **two-factor**: backup_codes JSON 문자열, secret TOTP 라이브러리로 생성
470
- - **organization**: 3 테이블 FK 관계. Migration 순서: Organization → Member, Invitation
471
- - **passkey**: public_key WebAuthn 표준, counter replay 방지용
472
- - **SSO**: metadata_url에서 IdP 메타데이터 자동 로드
468
+ - **OAuth**: different fields used per provider. access_token/refresh_token/password all optional in SaveParams
469
+ - **two-factor**: backup_codes is a JSON string, secret is generated by a TOTP library
470
+ - **organization**: 3-table FK relationships. Migration order: Organization → Member, Invitation
471
+ - **passkey**: public_key is WebAuthn standard, counter is for replay prevention
472
+ - **SSO**: IdP metadata is automatically loaded from metadata_url
473
473
 
474
474
  ## Common Mistakes
475
475
 
476
- ### 실수 1: Migration을 순서대로 개별 적용
476
+ ### Mistake 1: Applying Migrations Individually in Order
477
477
 
478
478
  ```bash
479
- # 잘못된 방법
480
- pnpm migration:apply # accounts.user_id 변경이 먼저 실행되어 실패
479
+ # wrong approach
480
+ pnpm migration:apply # fails because accounts.user_id change runs first
481
481
  ```
482
482
 
483
- 이유: accounts.user_id text 변경하려 할 때 users.id 아직 integer이므로 FK constraint 위반
483
+ Reason: when trying to change accounts.user_id to text, users.id is still integer, so FK constraint violation
484
484
 
485
- 올바른 방법: 하나의 migration에서 모든 변경 처리
485
+ Correct approach: handle all changes in a single migration
486
486
 
487
- ### 실수 2: test-helpers 타입 미수정
487
+ ### Mistake 2: Not Updating test-helpers Types
488
488
 
489
489
  ```typescript
490
- // User.id string인데 헬퍼 함수는 number 반환
490
+ // helper function returns number even though User.id is string
491
491
  async function createTestUser(): Promise<number> { ... }
492
492
 
493
- // 사용 타입 에러
494
- const userId = await createTestUser(); // string number에 할당 불가
493
+ // type error on use
494
+ const userId = await createTestUser(); // cannot assign string to number
495
495
  await createTestProjectParticipant(projectId, userId);
496
496
  ```
497
497
 
498
- 수정 필요:
498
+ Fixes needed:
499
499
 
500
- - createTestUser 반환 타입: string
501
- - userId를 받는 모든 헬퍼 함수 파라미터: string
500
+ - createTestUser return type: string
501
+ - All helper function parameters that receive userId: string
502
502
 
503
- ### 실수 3: HasMany joinColumn 누락
503
+ ### Mistake 3: Missing HasMany joinColumn
504
504
 
505
505
  ```json
506
506
  // Parent
507
507
  { "name": "files", "relationType": "HasMany", "joinColumn": "entity_id" }
508
508
 
509
- // Child entity_id 없으면 에러
509
+ // error if Child does not have entity_id
510
510
  ```
511
511
 
512
- 에러 메시지: `column files.entity_id does not exist`
512
+ Error message: `column files.entity_id does not exist`
513
513
 
514
- 해결: Child 엔티티에 joinColumn 지정한 컬럼 추가
514
+ Fix: add the column specified in joinColumn to the Child entity
515
515
 
516
- ### 실수 4: SaveParams에 nullable 필드를 optional로 처리하지 않음
516
+ ### Mistake 4: Not Making Nullable Fields Optional in SaveParams
517
517
 
518
518
  ```typescript
519
- // SaveParams에서 password optional 안하면
519
+ // if password is not optional in SaveParams
520
520
  await AccountModel.save([
521
521
  {
522
522
  provider_id: "google",
523
- // password 없으면 타입 에러
523
+ // type error if password is absent
524
524
  },
525
525
  ]);
526
526
  ```
527
527
 
528
- ### 실수 5: 중복 migration 미정리
528
+ ### Mistake 5: Not Cleaning Up Duplicate Migrations
529
529
 
530
- Entity 변경 generate하면 개별 migration + 통합 migration 생성됨. 개별 migration들을 제거하지 않으면 순서대로 실행되어 FK constraint 위반
530
+ After entity changes, generating creates both individual migrations and a consolidated migration. If the individual migrations are not removed, they run in order and violate FK constraints
531
531
 
532
- ### 실수 6: PluginSchema 필드명을 Sonamu Entity에 그대로 사용
532
+ ### Mistake 6: Using PluginSchema Field Names Directly in Sonamu Entity
533
533
 
534
- better-auth의 camelCase(`phoneNumber`)가 아닌 snake_case(`phone_number`) Sonamu Entity에서 사용해야 함. better-auth camelCase→snake_case 자동 변환.
534
+ Must use snake_case (`phone_number`) in Sonamu Entity, not better-auth's camelCase (`phoneNumber`). better-auth automatically converts camelCase snake_case.
535
535
 
536
- ### 실수 7: 테이블 생성 FK를 테이블 생성과 동시에 추가
536
+ ### Mistake 7: Adding FK at the Same Time as Table Creation for New Tables
537
537
 
538
- 테이블 생성과 FK 추가를 분리해야 함. 테이블 생성 `foreign()`을 함께 쓰면 참조 테이블이 아직 없을 있음. "Migration 패턴" 참조.
538
+ Table creation and FK addition must be separated. Using `foreign()` together with table creation may reference a table that does not exist yet. See "Migration Patterns" above.
539
539
 
540
540
  ## Checklist
541
541
 
542
- **Entity 수정:**
542
+ **Entity updates:**
543
543
 
544
- - [ ] User.id 타입을 string으로 변경
545
- - [ ] User를 참조하는 모든 FK 엔티티 확인 (grep으로 검색)
546
- - [ ] HasMany 관계가 있다면 joinColumn 컬럼이 자식 엔티티에 존재하는지 확인
547
- - [ ] better-auth 플러그인별 필요한 필드 확인 (기존 테이블 확장 vs 테이블)
544
+ - [ ] Change User.id type to string
545
+ - [ ] Check all FK entities referencing User (search with grep)
546
+ - [ ] If there are HasMany relationships, confirm the joinColumn column exists in the child entity
547
+ - [ ] Check required fields per better-auth plugin (extend existing table vs. new table)
548
548
 
549
- **Migration 작성:**
549
+ **Writing Migration:**
550
550
 
551
- - [ ] 통합 migration 작성 (FK 제거 - 타입 변경 - FK 복구 순서)
552
- - [ ] 중복 생성된 개별 migration 파일 삭제
553
- - [ ] down 함수도 올바른 순서로 작성
554
- - [ ] 테이블 생성 FK 순서 확인 (테이블 생성 → FK 추가)
551
+ - [ ] Write consolidated migration (FK removal type change FK restore order)
552
+ - [ ] Delete individually generated duplicate migration files
553
+ - [ ] Write down function in correct order too
554
+ - [ ] Check FK order when creating new tables (create tableadd FK)
555
555
 
556
- **타입 정의:**
556
+ **Type definitions:**
557
557
 
558
- - [ ] SaveParams에 nullable 필드 모두 partial 처리
559
- - [ ] SaveParams에 dbDefault 필드(created_at, updated_at) partial 처리
560
- - [ ] test-helpers userId 관련 파라미터를 string으로 수정
561
- - [ ] test-helpers 반환 타입 수정 (Promise<String> -> Promise<string>)
558
+ - [ ] Make all nullable fields partial in SaveParams
559
+ - [ ] Make dbDefault fields (created_at, updated_at) partial in SaveParams
560
+ - [ ] Change userId-related parameters in test-helpers to string
561
+ - [ ] Fix return types in test-helpers (Promise<String> -> Promise<string>)
562
562
 
563
- **테스트 코드:**
563
+ **Test code:**
564
564
 
565
- - [ ] 테스트에서 불필요한 nullable 필드 제거
566
- - [ ] OAuth 계정과 credential 계정 테스트 분리
567
- - [ ] provider에 맞는 필드만 제공
568
- - [ ] 플러그인별 테스트 케이스 작성 (phone-number, two-factor )
565
+ - [ ] Remove unnecessary nullable fields from tests
566
+ - [ ] Separate tests for OAuth accounts and credential accounts
567
+ - [ ] Provide only fields appropriate to each provider
568
+ - [ ] Write test cases per plugin (phone-number, two-factor, etc.)
569
569
 
570
- **실행:**
570
+ **Execution:**
571
571
 
572
- - [ ] stub 재생성: `pnpm stub`
573
- - [ ] Migration 생성: `pnpm generate`
574
- - [ ] 중복 migration 정리
575
- - [ ] Migration 적용: `pnpm migration:apply`
576
- - [ ] 전체 테스트 실행: `pnpm test`
572
+ - [ ] Regenerate stubs: `pnpm stub`
573
+ - [ ] Generate migration: `pnpm generate`
574
+ - [ ] Clean up duplicate migrations
575
+ - [ ] Apply migration: `pnpm migration:apply`
576
+ - [ ] Run all tests: `pnpm test`
577
577
 
578
- ## Better-auth 엔티티 Fixture 생성
578
+ ## Generating better-auth Entity Fixtures
579
579
 
580
- ### 생성 순서 (필수)
580
+ ### Generation Order (Required)
581
581
 
582
- better-auth 엔티티는 FK 의존성 때문에 반드시 다음 순서로 fixture를 생성해야 한다.
582
+ better-auth entities must have fixtures generated in the following order due to FK dependencies.
583
583
 
584
584
  ```
585
- User → Account → Session → Verification (선택)
585
+ User → Account → Session → Verification (optional)
586
586
  ```
587
587
 
588
- Account, Session user_id(string FK) 통해 User 참조하므로 User가 먼저 생성되어야 한다.
588
+ Account and Session reference User via user_id (string FK), so User must be created first.
589
589
 
590
- ### 생성 명령
590
+ ### Generation Commands
591
591
 
592
592
  ```bash
593
- # 1. User 먼저 생성
593
+ # 1. Generate User first
594
594
  pnpm sonamu fixture gen --include User --count 10 --use-llm
595
595
 
596
- # 2. Account 생성 (User에 의존)
596
+ # 2. Generate Account (depends on User)
597
597
  pnpm sonamu fixture gen --include Account --count 10 --use-llm
598
598
 
599
- # 3. Session 생성 (User에 의존)
599
+ # 3. Generate Session (depends on User)
600
600
  pnpm sonamu fixture gen --include Session --count 10 --use-llm
601
601
 
602
- # 또는 User를 포함하여 함께 생성 (자동 순서 정렬)
602
+ # Or generate together including User (auto-sorted order)
603
603
  pnpm sonamu fixture gen --include User,Account,Session --count 10 --use-llm
604
604
  ```
605
605
 
606
- ### User.id 시퀀스 설정 필수
606
+ ### User.id Sequence Setup Required
607
607
 
608
- better-auth User 엔티티는 id string 타입이지만, fixture gen 자동으로 숫자 시퀀스를 사용한다. **PHASE 0에서 users_id_seq 생성하지 않았다면 fixture gen 실패한다.**
608
+ The better-auth User entity has id as string type, but fixture gen automatically uses a numeric sequence. **If users_id_seq was not created in PHASE 0, fixture gen will fail.**
609
609
 
610
610
  ```sql
611
- -- 반드시 먼저 설정되어 있어야
611
+ -- must be set up in advance
612
612
  CREATE SEQUENCE users_id_seq;
613
613
  ALTER TABLE users ALTER COLUMN id SET DEFAULT nextval('users_id_seq')::text;
614
614
  ```
615
615
 
616
- 이미 설정되어 있어야 하지만 누락된 경우 쿼리를 실행한 fixture gen을 진행한다.
616
+ If it has already been set up but is missing, run the above query before proceeding with fixture gen.
617
617
 
618
- ### cone.fixtureStrategy 설정 권장
618
+ ### cone.fixtureStrategy Configuration Recommended
619
619
 
620
- User entity.json의 id prop에 `"fixtureStrategy": "sequence"`가 설정되어 있는지 확인한다:
620
+ Check that `"fixtureStrategy": "sequence"` is set on the id prop in User entity.json:
621
621
 
622
622
  ```json
623
623
  {
@@ -625,17 +625,17 @@ User entity.json의 id prop에 `"fixtureStrategy": "sequence"`가 설정되어
625
625
  "type": "string",
626
626
  "cone": {
627
627
  "fixtureStrategy": "sequence",
628
- "note": "better-auth 관리하는 사용자 ID (string 타입)"
628
+ "note": "User ID managed by better-auth (string type)"
629
629
  }
630
630
  }
631
631
  ```
632
632
 
633
- ### Account 생성 주의사항
633
+ ### Notes When Generating Account
634
634
 
635
- Account credential 계정과 OAuth 계정의 구조가 다르다:
635
+ Account has different structure for credential accounts and OAuth accounts:
636
636
 
637
637
  ```typescript
638
- // credential 계정 (이메일/비밀번호)
638
+ // credential account (email/password)
639
639
  {
640
640
  provider_id: "credential",
641
641
  account_id: "user@example.com",
@@ -643,21 +643,21 @@ Account는 credential 계정과 OAuth 계정의 구조가 다르다:
643
643
  password: hashedPassword,
644
644
  }
645
645
 
646
- // OAuth 계정 (Google )
646
+ // OAuth account (Google, etc.)
647
647
  {
648
648
  provider_id: "google",
649
649
  account_id: "google-oauth-id-12345",
650
650
  user_id: existingUserId,
651
- // password 없음
651
+ // no password
652
652
  }
653
653
  ```
654
654
 
655
- `--use-llm`과 cone.note 적절히 설정하면 LLM 맥락에 맞는 provider_id account_id를 생성해준다.
655
+ Setting `--use-llm` and cone.note appropriately allows the LLM to generate contextually appropriate provider_id and account_id.
656
656
 
657
657
  ## Related Skills
658
658
 
659
- - migration: Migration 작성 기본, PK 타입 변경
660
- - entity-basic: Entity 타입 정의
661
- - entity-relations: BelongsToOne, HasMany 관계
662
- - testing: 테스트 작성 패턴
663
- - fixture-cli: Fixture 생성 CLI 사용법
659
+ - migration: Migration basics, PK type changes
660
+ - entity-basic: Entity type definitions
661
+ - entity-relations: BelongsToOne, HasMany relationships
662
+ - testing: Test writing patterns
663
+ - fixture-cli: Fixture generation CLI usage