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,24 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sonamu-auth-migration
|
|
3
|
-
description:
|
|
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
|
|
6
|
+
# Auth System Migration (Integrating External Auth such as better-auth)
|
|
7
7
|
|
|
8
8
|
## Situation
|
|
9
9
|
|
|
10
|
-
|
|
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
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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
|
-
|
|
30
|
+
Note: changing from integer to string
|
|
31
31
|
|
|
32
|
-
### 2.
|
|
32
|
+
### 2. Identify Affected FKs
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
#
|
|
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 (
|
|
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
|
-
|
|
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(); //
|
|
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
|
|
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
|
|
79
|
+
// Step 2: Remove PK constraint
|
|
80
80
|
await knex.raw('ALTER TABLE "users" DROP CONSTRAINT "users_pkey"');
|
|
81
81
|
|
|
82
|
-
// 3
|
|
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
|
|
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
|
|
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.
|
|
129
|
-
2.
|
|
130
|
-
3.
|
|
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.
|
|
132
|
+
### 4. Notes When Regenerating Migrations
|
|
133
133
|
|
|
134
|
-
|
|
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
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
162
|
+
Fix 2: Remove user_id-related changes from individual migrations
|
|
163
163
|
|
|
164
|
-
- accounts, sessions
|
|
165
|
-
- evaluation_committees, project_participants, reports
|
|
166
|
-
-
|
|
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
|
|
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
|
|
176
|
-
created_at: true, //
|
|
177
|
-
updated_at: true, //
|
|
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
|
|
191
|
-
- created_at, updated_at: optional (
|
|
192
|
-
-
|
|
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
|
-
//
|
|
197
|
+
// If password is not optional in SaveParams
|
|
198
198
|
await AccountModel.save([
|
|
199
199
|
{
|
|
200
200
|
provider_id: "google",
|
|
201
|
-
//
|
|
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
|
-
|
|
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
|
-
-
|
|
259
|
-
- nullable
|
|
260
|
-
- dbDefault
|
|
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
|
|
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
|
-
#
|
|
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.
|
|
295
|
+
### 8. Handling joinColumn in HasMany Relationships
|
|
296
296
|
|
|
297
|
-
HasMany
|
|
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": "
|
|
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
|
-
-
|
|
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
|
|
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
|
-
//
|
|
364
|
+
// stored as phone_number in DB (snake_case)
|
|
365
365
|
```
|
|
366
366
|
|
|
367
|
-
Sonamu Entity
|
|
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
|
-
|
|
|
388
|
-
|
|
|
389
|
-
|
|
|
390
|
-
|
|
|
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
|
-
|
|
394
|
+
**Only existing table extension needed (add fields to User/Session)**: username, phone number, admin, anonymous, multi-session
|
|
395
395
|
|
|
396
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
413
|
-
- FK(`user_id`)
|
|
414
|
-
- `nullable`
|
|
415
|
-
- better-auth
|
|
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
|
-
|
|
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
|
-
|
|
429
|
+
**Creating new tables** — create without FK first, then add FK (must be separated!):
|
|
430
430
|
|
|
431
431
|
```typescript
|
|
432
|
-
// 1
|
|
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
|
|
435
|
+
table.text("user_id").notNullable(); // FK column only, no foreign()
|
|
436
436
|
table.text("secret").notNullable();
|
|
437
437
|
});
|
|
438
|
-
// 2
|
|
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
|
-
|
|
446
|
+
Currently implemented plugins in Sonamu:
|
|
447
447
|
|
|
448
|
-
- phone-number
|
|
449
|
-
- two-factor
|
|
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
|
-
-
|
|
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
|
|
460
|
-
2. Migration
|
|
461
|
-
3. SaveParams
|
|
462
|
-
4. Model
|
|
463
|
-
5. test-helpers
|
|
464
|
-
6.
|
|
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**:
|
|
469
|
-
- **two-factor**: backup_codes
|
|
470
|
-
- **organization**: 3
|
|
471
|
-
- **passkey**: public_key
|
|
472
|
-
- **SSO**:
|
|
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
|
-
###
|
|
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
|
-
|
|
483
|
+
Reason: when trying to change accounts.user_id to text, users.id is still integer, so FK constraint violation
|
|
484
484
|
|
|
485
|
-
|
|
485
|
+
Correct approach: handle all changes in a single migration
|
|
486
486
|
|
|
487
|
-
###
|
|
487
|
+
### Mistake 2: Not Updating test-helpers Types
|
|
488
488
|
|
|
489
489
|
```typescript
|
|
490
|
-
// User.id
|
|
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
|
|
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
|
|
501
|
-
-
|
|
500
|
+
- createTestUser return type: string
|
|
501
|
+
- All helper function parameters that receive userId: string
|
|
502
502
|
|
|
503
|
-
###
|
|
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
|
|
509
|
+
// error if Child does not have entity_id
|
|
510
510
|
```
|
|
511
511
|
|
|
512
|
-
|
|
512
|
+
Error message: `column files.entity_id does not exist`
|
|
513
513
|
|
|
514
|
-
|
|
514
|
+
Fix: add the column specified in joinColumn to the Child entity
|
|
515
515
|
|
|
516
|
-
###
|
|
516
|
+
### Mistake 4: Not Making Nullable Fields Optional in SaveParams
|
|
517
517
|
|
|
518
518
|
```typescript
|
|
519
|
-
//
|
|
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
|
-
###
|
|
528
|
+
### Mistake 5: Not Cleaning Up Duplicate Migrations
|
|
529
529
|
|
|
530
|
-
|
|
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
|
-
###
|
|
532
|
+
### Mistake 6: Using PluginSchema Field Names Directly in Sonamu Entity
|
|
533
533
|
|
|
534
|
-
|
|
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
|
-
###
|
|
536
|
+
### Mistake 7: Adding FK at the Same Time as Table Creation for New Tables
|
|
537
537
|
|
|
538
|
-
|
|
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
|
|
545
|
-
- [ ]
|
|
546
|
-
- [ ] HasMany
|
|
547
|
-
- [ ] better-auth
|
|
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
|
-
- [ ]
|
|
552
|
-
- [ ]
|
|
553
|
-
- [ ] down
|
|
554
|
-
- [ ]
|
|
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 table → add FK)
|
|
555
555
|
|
|
556
|
-
|
|
556
|
+
**Type definitions:**
|
|
557
557
|
|
|
558
|
-
- [ ]
|
|
559
|
-
- [ ]
|
|
560
|
-
- [ ] test-helpers
|
|
561
|
-
- [ ] test-helpers
|
|
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
|
-
- [ ]
|
|
566
|
-
- [ ] OAuth
|
|
567
|
-
- [ ]
|
|
568
|
-
- [ ]
|
|
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
|
-
- [ ]
|
|
573
|
-
- [ ]
|
|
574
|
-
- [ ]
|
|
575
|
-
- [ ]
|
|
576
|
-
- [ ]
|
|
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
|
-
##
|
|
578
|
+
## Generating better-auth Entity Fixtures
|
|
579
579
|
|
|
580
|
-
###
|
|
580
|
+
### Generation Order (Required)
|
|
581
581
|
|
|
582
|
-
better-auth
|
|
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
|
|
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
|
|
596
|
+
# 2. Generate Account (depends on User)
|
|
597
597
|
pnpm sonamu fixture gen --include Account --count 10 --use-llm
|
|
598
598
|
|
|
599
|
-
# 3. Session
|
|
599
|
+
# 3. Generate Session (depends on User)
|
|
600
600
|
pnpm sonamu fixture gen --include Session --count 10 --use-llm
|
|
601
601
|
|
|
602
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
628
|
+
"note": "User ID managed by better-auth (string type)"
|
|
629
629
|
}
|
|
630
630
|
}
|
|
631
631
|
```
|
|
632
632
|
|
|
633
|
-
###
|
|
633
|
+
### Notes When Generating Account
|
|
634
634
|
|
|
635
|
-
Account
|
|
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
|
|
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
|
|
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
|
|
660
|
-
- entity-basic: Entity
|
|
661
|
-
- entity-relations: BelongsToOne, HasMany
|
|
662
|
-
- testing:
|
|
663
|
-
- fixture-cli: Fixture
|
|
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
|