sonamu 0.10.7 → 0.10.8
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/bin/cli.js +10 -253
- package/dist/migration/code-generation.js +2 -2
- package/dist/ui-web/assets/{index-D0MHYbxl.js → index-CJf8uJYf.js} +47 -41
- package/dist/ui-web/assets/index-GMMIVGja.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/package.json +3 -3
- package/src/bin/cli.ts +12 -375
- package/src/migration/code-generation.ts +1 -1
- package/dist/ui-web/assets/index-Dx_JX4aQ.css +0 -1
- package/src/skills/AGENTS.md +0 -108
- package/src/skills/sonamu/SKILL.md +0 -75
- package/src/skills/sonamu-ai-agents/SKILL.md +0 -205
- package/src/skills/sonamu-api/SKILL.md +0 -480
- package/src/skills/sonamu-auth/SKILL.md +0 -327
- package/src/skills/sonamu-auth/references/plugins.md +0 -310
- package/src/skills/sonamu-auth/references/user-id-migration-followups.md +0 -192
- package/src/skills/sonamu-auth/references/user-id-migration.md +0 -455
- package/src/skills/sonamu-config/SKILL.md +0 -203
- package/src/skills/sonamu-config/references/database.md +0 -452
- package/src/skills/sonamu-config/references/environments.md +0 -178
- package/src/skills/sonamu-config/references/server-options.md +0 -400
- package/src/skills/sonamu-entity/SKILL.md +0 -180
- package/src/skills/sonamu-entity/references/creation-workflow.md +0 -581
- package/src/skills/sonamu-entity/references/design-guides.md +0 -243
- package/src/skills/sonamu-entity/references/field-types.md +0 -170
- package/src/skills/sonamu-entity/references/relations-detail.md +0 -245
- package/src/skills/sonamu-entity/references/relations.md +0 -463
- package/src/skills/sonamu-entity/references/subset.md +0 -156
- package/src/skills/sonamu-fixture/SKILL.md +0 -180
- package/src/skills/sonamu-fixture/references/cli-usage.md +0 -439
- package/src/skills/sonamu-fixture/references/cone.md +0 -298
- package/src/skills/sonamu-frontend/SKILL.md +0 -142
- package/src/skills/sonamu-frontend/references/components.md +0 -323
- package/src/skills/sonamu-frontend/references/examples.md +0 -64
- package/src/skills/sonamu-frontend/references/hooks.md +0 -273
- package/src/skills/sonamu-frontend/references/runtime.md +0 -165
- package/src/skills/sonamu-frontend/references/scaffolding.md +0 -439
- package/src/skills/sonamu-i18n/SKILL.md +0 -287
- package/src/skills/sonamu-migration/SKILL.md +0 -316
- package/src/skills/sonamu-naite/SKILL.md +0 -266
- package/src/skills/sonamu-query/SKILL.md +0 -48
- package/src/skills/sonamu-query/references/model-patterns.md +0 -390
- package/src/skills/sonamu-query/references/model.md +0 -366
- package/src/skills/sonamu-query/references/puri.md +0 -413
- package/src/skills/sonamu-query/references/search.md +0 -238
- package/src/skills/sonamu-query/references/upsert.md +0 -324
- package/src/skills/sonamu-tasks/SKILL.md +0 -236
- package/src/skills/sonamu-testing/SKILL.md +0 -251
- package/src/skills/sonamu-testing/references/devrunner.md +0 -405
- package/src/skills/sonamu-testing/references/helpers.md +0 -185
- package/src/skills/sonamu-testing/references/patterns.md +0 -263
- package/src/skills/sonamu-testing/references/pitfalls.md +0 -588
- package/src/skills/sonamu-testing/references/quick-start.md +0 -285
- package/src/skills/sonamu-testing/references/type-safety.md +0 -172
- package/src/skills/sonamu-testing/references/writing-plan.md +0 -375
- package/src/skills/sonamu-vector/SKILL.md +0 -222
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
# User.id Migration — Mistakes, Checklist, Fixtures
|
|
2
|
-
|
|
3
|
-
## Common Mistakes
|
|
4
|
-
|
|
5
|
-
### Mistake 1: Applying Migrations Individually in Order
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# wrong approach
|
|
9
|
-
pnpm migration:apply # fails because accounts.user_id change runs first
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
Reason: when trying to change accounts.user_id to text, users.id is still integer, so FK constraint violation
|
|
13
|
-
|
|
14
|
-
Correct approach: handle all changes in a single migration
|
|
15
|
-
|
|
16
|
-
### Mistake 2: Not Updating test-helpers Types
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
// helper function returns number even though User.id is string
|
|
20
|
-
async function createTestUser(): Promise<number> { ... }
|
|
21
|
-
|
|
22
|
-
// type error on use
|
|
23
|
-
const userId = await createTestUser(); // cannot assign string to number
|
|
24
|
-
await createTestProjectParticipant(projectId, userId);
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Fixes needed:
|
|
28
|
-
|
|
29
|
-
- createTestUser return type: string
|
|
30
|
-
- All helper function parameters that receive userId: string
|
|
31
|
-
|
|
32
|
-
### Mistake 3: Missing HasMany joinColumn
|
|
33
|
-
|
|
34
|
-
```json
|
|
35
|
-
// Parent
|
|
36
|
-
{ "name": "files", "relationType": "HasMany", "joinColumn": "entity_id" }
|
|
37
|
-
|
|
38
|
-
// error if Child does not have entity_id
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Error message: `column files.entity_id does not exist`
|
|
42
|
-
|
|
43
|
-
Fix: add the column specified in joinColumn to the Child entity
|
|
44
|
-
|
|
45
|
-
### Mistake 4: Not Making Nullable Fields Optional in SaveParams
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// if password is not optional in SaveParams
|
|
49
|
-
await AccountModel.save([
|
|
50
|
-
{
|
|
51
|
-
provider_id: "google",
|
|
52
|
-
// type error if password is absent
|
|
53
|
-
},
|
|
54
|
-
]);
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Mistake 5: Not Cleaning Up Duplicate Migrations
|
|
58
|
-
|
|
59
|
-
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
|
|
60
|
-
|
|
61
|
-
### Mistake 6: Using PluginSchema Field Names Directly in Sonamu Entity
|
|
62
|
-
|
|
63
|
-
Must use snake_case (`phone_number`) in Sonamu Entity, not better-auth's camelCase (`phoneNumber`). better-auth automatically converts camelCase → snake_case.
|
|
64
|
-
|
|
65
|
-
### Mistake 7: Adding FK at the Same Time as Table Creation for New Tables
|
|
66
|
-
|
|
67
|
-
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.
|
|
68
|
-
|
|
69
|
-
## Checklist
|
|
70
|
-
|
|
71
|
-
**Entity updates:**
|
|
72
|
-
|
|
73
|
-
- [ ] Change User.id type to string
|
|
74
|
-
- [ ] Check all FK entities referencing User (search with grep)
|
|
75
|
-
- [ ] If there are HasMany relationships, confirm the joinColumn column exists in the child entity
|
|
76
|
-
- [ ] Check required fields per better-auth plugin (extend existing table vs. new table)
|
|
77
|
-
|
|
78
|
-
**Writing Migration:**
|
|
79
|
-
|
|
80
|
-
- [ ] Write consolidated migration (FK removal → type change → FK restore order)
|
|
81
|
-
- [ ] Delete individually generated duplicate migration files
|
|
82
|
-
- [ ] Write down function in correct order too
|
|
83
|
-
- [ ] Check FK order when creating new tables (create table → add FK)
|
|
84
|
-
|
|
85
|
-
**Type definitions:**
|
|
86
|
-
|
|
87
|
-
- [ ] Make all nullable fields partial in SaveParams
|
|
88
|
-
- [ ] Make dbDefault fields (created_at, updated_at) partial in SaveParams
|
|
89
|
-
- [ ] Change userId-related parameters in test-helpers to string
|
|
90
|
-
- [ ] Fix return types in test-helpers (Promise<String> -> Promise<string>)
|
|
91
|
-
|
|
92
|
-
**Test code:**
|
|
93
|
-
|
|
94
|
-
- [ ] Remove unnecessary nullable fields from tests
|
|
95
|
-
- [ ] Separate tests for OAuth accounts and credential accounts
|
|
96
|
-
- [ ] Provide only fields appropriate to each provider
|
|
97
|
-
- [ ] Write test cases per plugin (phone-number, two-factor, etc.)
|
|
98
|
-
|
|
99
|
-
**Execution:**
|
|
100
|
-
|
|
101
|
-
- [ ] Regenerate stubs: `pnpm stub`
|
|
102
|
-
- [ ] Generate migration: `pnpm generate`
|
|
103
|
-
- [ ] Clean up duplicate migrations
|
|
104
|
-
- [ ] Apply migration: `pnpm migration:apply`
|
|
105
|
-
- [ ] Run all tests: `pnpm test`
|
|
106
|
-
|
|
107
|
-
## Generating better-auth Entity Fixtures
|
|
108
|
-
|
|
109
|
-
### Generation Order (Required)
|
|
110
|
-
|
|
111
|
-
better-auth entities must have fixtures generated in the following order due to FK dependencies.
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
User → Account → Session → Verification (optional)
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Account and Session reference User via user_id (string FK), so User must be created first.
|
|
118
|
-
|
|
119
|
-
### Generation Commands
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
# 1. Generate User first
|
|
123
|
-
pnpm sonamu fixture gen --include User --count 10 --use-llm
|
|
124
|
-
|
|
125
|
-
# 2. Generate Account (depends on User)
|
|
126
|
-
pnpm sonamu fixture gen --include Account --count 10 --use-llm
|
|
127
|
-
|
|
128
|
-
# 3. Generate Session (depends on User)
|
|
129
|
-
pnpm sonamu fixture gen --include Session --count 10 --use-llm
|
|
130
|
-
|
|
131
|
-
# Or generate together including User (auto-sorted order)
|
|
132
|
-
pnpm sonamu fixture gen --include User,Account,Session --count 10 --use-llm
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### User.id Sequence Setup Required
|
|
136
|
-
|
|
137
|
-
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 during initial project setup, fixture gen will fail.**
|
|
138
|
-
|
|
139
|
-
```sql
|
|
140
|
-
-- must be set up in advance
|
|
141
|
-
CREATE SEQUENCE users_id_seq;
|
|
142
|
-
ALTER TABLE users ALTER COLUMN id SET DEFAULT nextval('users_id_seq')::text;
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
If it has already been set up but is missing, run the above query before proceeding with fixture gen.
|
|
146
|
-
|
|
147
|
-
### cone.fixtureStrategy Configuration Recommended
|
|
148
|
-
|
|
149
|
-
Check that `"fixtureStrategy": "sequence"` is set on the id prop in User entity.json:
|
|
150
|
-
|
|
151
|
-
```json
|
|
152
|
-
{
|
|
153
|
-
"name": "id",
|
|
154
|
-
"type": "string",
|
|
155
|
-
"cone": {
|
|
156
|
-
"fixtureStrategy": "sequence",
|
|
157
|
-
"note": "User ID managed by better-auth (string type)"
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Notes When Generating Account
|
|
163
|
-
|
|
164
|
-
Account has different structure for credential accounts and OAuth accounts:
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
// credential account (email/password)
|
|
168
|
-
{
|
|
169
|
-
provider_id: "credential",
|
|
170
|
-
account_id: "user@example.com",
|
|
171
|
-
user_id: existingUserId,
|
|
172
|
-
password: hashedPassword,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// OAuth account (Google, etc.)
|
|
176
|
-
{
|
|
177
|
-
provider_id: "google",
|
|
178
|
-
account_id: "google-oauth-id-12345",
|
|
179
|
-
user_id: existingUserId,
|
|
180
|
-
// no password
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Setting `--use-llm` and cone.note appropriately allows the LLM to generate contextually appropriate provider_id and account_id.
|
|
185
|
-
|
|
186
|
-
## Related Skills
|
|
187
|
-
|
|
188
|
-
- migration: Migration basics, PK type changes
|
|
189
|
-
- entity-basic: Entity type definitions
|
|
190
|
-
- entity-relations: BelongsToOne, HasMany relationships
|
|
191
|
-
- testing: Test writing patterns
|
|
192
|
-
- fixture-cli: Fixture generation CLI usage
|
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
# User.id Type Migration for External Auth
|
|
2
|
-
|
|
3
|
-
# Auth System Migration (Integrating External Auth such as better-auth)
|
|
4
|
-
|
|
5
|
-
## Situation
|
|
6
|
-
|
|
7
|
-
When User.id type change is needed to integrate an external authentication system (better-auth, NextAuth, etc.) with an existing Sonamu project
|
|
8
|
-
|
|
9
|
-
## Problem
|
|
10
|
-
|
|
11
|
-
- better-auth requires User.id to be of type string (text)
|
|
12
|
-
- The existing system uses integer type
|
|
13
|
-
- All FKs referencing User must be changed together
|
|
14
|
-
- FK constraint violations occur if migration order is wrong
|
|
15
|
-
|
|
16
|
-
## Solution
|
|
17
|
-
|
|
18
|
-
### 1. Change Entity Type
|
|
19
|
-
|
|
20
|
-
```json
|
|
21
|
-
// user.entity.json
|
|
22
|
-
{
|
|
23
|
-
"props": [{ "name": "id", "type": "string", "desc": "ID" }]
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Note: changing from integer to string
|
|
28
|
-
|
|
29
|
-
### 2. Identify Affected FKs
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
# Find all relations referencing User
|
|
33
|
-
grep -r "with.*User" --include="*.entity.json"
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Tables commonly affected:
|
|
37
|
-
|
|
38
|
-
- accounts.user_id
|
|
39
|
-
- sessions.user_id
|
|
40
|
-
- evaluation_committees.evaluator_id (or other User-referencing FKs)
|
|
41
|
-
- project_participants.user_id
|
|
42
|
-
- reports.submitted_by_id
|
|
43
|
-
|
|
44
|
-
### 3. Migration Write Order (Required)
|
|
45
|
-
|
|
46
|
-
Wrong order - FK constraint violation:
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
// wrong example
|
|
50
|
-
await knex.schema.alterTable("accounts", (table) => {
|
|
51
|
-
table.text("user_id").alter(); // fails: FK still references users.id(integer)
|
|
52
|
-
});
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Correct order:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
export async function up(knex: Knex): Promise<void> {
|
|
59
|
-
// Step 1: Remove all FK constraints
|
|
60
|
-
await knex.raw('ALTER TABLE "accounts" DROP CONSTRAINT "accounts_user_id_foreign"');
|
|
61
|
-
await knex.raw('ALTER TABLE "sessions" DROP CONSTRAINT "sessions_user_id_foreign"');
|
|
62
|
-
await knex.raw(
|
|
63
|
-
'ALTER TABLE "evaluation_committees" DROP CONSTRAINT "evaluation_committees_evaluator_id_foreign"',
|
|
64
|
-
);
|
|
65
|
-
await knex.raw(
|
|
66
|
-
'ALTER TABLE "project_participants" DROP CONSTRAINT "project_participants_user_id_foreign"',
|
|
67
|
-
);
|
|
68
|
-
await knex.raw('ALTER TABLE "reports" DROP CONSTRAINT "reports_submitted_by_id_foreign"');
|
|
69
|
-
|
|
70
|
-
// Step 2: Remove PK constraint
|
|
71
|
-
await knex.raw('ALTER TABLE "users" DROP CONSTRAINT "users_pkey"');
|
|
72
|
-
|
|
73
|
-
// Step 3: Change all column types (parent PK + child FKs)
|
|
74
|
-
await knex.raw('ALTER TABLE "users" ALTER COLUMN "id" TYPE text USING "id"::text');
|
|
75
|
-
await knex.raw('ALTER TABLE "accounts" ALTER COLUMN "user_id" TYPE text USING "user_id"::text');
|
|
76
|
-
await knex.raw('ALTER TABLE "sessions" ALTER COLUMN "user_id" TYPE text USING "user_id"::text');
|
|
77
|
-
await knex.raw(
|
|
78
|
-
'ALTER TABLE "evaluation_committees" ALTER COLUMN "evaluator_id" TYPE text USING "evaluator_id"::text',
|
|
79
|
-
);
|
|
80
|
-
await knex.raw(
|
|
81
|
-
'ALTER TABLE "project_participants" ALTER COLUMN "user_id" TYPE text USING "user_id"::text',
|
|
82
|
-
);
|
|
83
|
-
await knex.raw(
|
|
84
|
-
'ALTER TABLE "reports" ALTER COLUMN "submitted_by_id" TYPE text USING "submitted_by_id"::text',
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
// Step 4: Restore PK constraint
|
|
88
|
-
await knex.raw('ALTER TABLE "users" ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id")');
|
|
89
|
-
|
|
90
|
-
// Step 5: Restore FK constraints
|
|
91
|
-
await knex.raw(
|
|
92
|
-
'ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_foreign" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON UPDATE RESTRICT ON DELETE CASCADE',
|
|
93
|
-
);
|
|
94
|
-
await knex.raw(
|
|
95
|
-
'ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_foreign" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON UPDATE RESTRICT ON DELETE CASCADE',
|
|
96
|
-
);
|
|
97
|
-
await knex.raw(
|
|
98
|
-
'ALTER TABLE "evaluation_committees" ADD CONSTRAINT "evaluation_committees_evaluator_id_foreign" FOREIGN KEY ("evaluator_id") REFERENCES "users"("id") ON UPDATE RESTRICT ON DELETE RESTRICT',
|
|
99
|
-
);
|
|
100
|
-
await knex.raw(
|
|
101
|
-
'ALTER TABLE "project_participants" ADD CONSTRAINT "project_participants_user_id_foreign" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON UPDATE RESTRICT ON DELETE RESTRICT',
|
|
102
|
-
);
|
|
103
|
-
await knex.raw(
|
|
104
|
-
'ALTER TABLE "reports" ADD CONSTRAINT "reports_submitted_by_id_foreign" FOREIGN KEY ("submitted_by_id") REFERENCES "users"("id") ON UPDATE RESTRICT ON DELETE RESTRICT',
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Core principles:
|
|
110
|
-
|
|
111
|
-
1. Column type cannot be changed while FK constraints exist
|
|
112
|
-
2. Remove all FKs, then change types, then restore FKs
|
|
113
|
-
3. Handle all changes in a single migration
|
|
114
|
-
|
|
115
|
-
### 4. Notes When Regenerating Migrations
|
|
116
|
-
|
|
117
|
-
Running `pnpm generate` after entity changes creates duplicate migrations:
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
20260203154926_alter_accounts_alter5.ts (changes accounts.user_id only)
|
|
121
|
-
20260203154927_alter_evaluation_committees.ts (changes evaluator_id only)
|
|
122
|
-
20260203154928_alter_project_participants.ts (changes user_id only)
|
|
123
|
-
20260203154929_alter_reports.ts (changes submitted_by_id only)
|
|
124
|
-
20260203154930_alter_sessions.ts (changes user_id only)
|
|
125
|
-
20260203154931_alter_users_pk_type.ts (consolidated: changes all types)
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Problems:
|
|
129
|
-
|
|
130
|
-
- Individual migrations (154926-154930) attempt to change only FK types
|
|
131
|
-
- The consolidated migration (154931) changes the same columns
|
|
132
|
-
- Running in order causes 154926 to execute first and violate FK constraints
|
|
133
|
-
|
|
134
|
-
Fix 1: Delete individual migrations
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
rm 20260203154926_alter_accounts_alter5.ts
|
|
138
|
-
rm 20260203154927_alter_evaluation_committees.ts
|
|
139
|
-
rm 20260203154928_alter_project_participants.ts
|
|
140
|
-
rm 20260203154929_alter_reports.ts
|
|
141
|
-
rm 20260203154930_alter_sessions.ts
|
|
142
|
-
# Keep only 20260203154931_alter_users_pk_type.ts
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Fix 2: Remove user_id-related changes from individual migrations
|
|
146
|
-
|
|
147
|
-
- Leave only updated_at changes in accounts, sessions migrations and remove user_id changes
|
|
148
|
-
- Delete evaluation_committees, project_participants, reports migrations
|
|
149
|
-
- Perform type changes in the consolidated migration only
|
|
150
|
-
|
|
151
|
-
### 5. SaveParams Type Definitions
|
|
152
|
-
|
|
153
|
-
Auth-related entities have many nullable fields, so all must be treated as optional in SaveParams:
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
// account.types.ts
|
|
157
|
-
export const AccountSaveParams = AccountBaseSchema.partial({
|
|
158
|
-
id: true, // to distinguish create vs update
|
|
159
|
-
created_at: true, // auto-generated via dbDefault
|
|
160
|
-
updated_at: true, // auto-generated via dbDefault
|
|
161
|
-
access_token: true, // nullable - OAuth only
|
|
162
|
-
refresh_token: true, // nullable - OAuth only
|
|
163
|
-
id_token: true, // nullable - OAuth only
|
|
164
|
-
access_token_expires_at: true, // nullable
|
|
165
|
-
refresh_token_expires_at: true, // nullable
|
|
166
|
-
scope: true, // nullable - OAuth only
|
|
167
|
-
password: true, // nullable - credential only
|
|
168
|
-
});
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Principles:
|
|
172
|
-
|
|
173
|
-
- id: optional (generated on create, required on update)
|
|
174
|
-
- created_at, updated_at: optional (auto-generated via dbDefault)
|
|
175
|
-
- All fields with nullable: true in entity: optional
|
|
176
|
-
|
|
177
|
-
Not doing this causes type errors when writing tests:
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
// If password is not optional in SaveParams
|
|
181
|
-
await AccountModel.save([
|
|
182
|
-
{
|
|
183
|
-
provider_id: "google",
|
|
184
|
-
// type error if password field is not provided
|
|
185
|
-
},
|
|
186
|
-
]);
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### 6. Test Writing Patterns
|
|
190
|
-
|
|
191
|
-
Bad example - providing unnecessary fields for OAuth account:
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
await AccountModel.save([
|
|
195
|
-
{
|
|
196
|
-
id: `acc_${Date.now()}`,
|
|
197
|
-
account_id: "google_123",
|
|
198
|
-
provider_id: "google",
|
|
199
|
-
user_id: userId,
|
|
200
|
-
access_token: "token_123",
|
|
201
|
-
refresh_token: "refresh_123",
|
|
202
|
-
id_token: "id_token_123",
|
|
203
|
-
password: "hashed_password", // unnecessary for OAuth
|
|
204
|
-
scope: "openid profile email",
|
|
205
|
-
access_token_expires_at: new Date(),
|
|
206
|
-
refresh_token_expires_at: new Date(),
|
|
207
|
-
created_at: new Date(),
|
|
208
|
-
updated_at: new Date(),
|
|
209
|
-
},
|
|
210
|
-
]);
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Good example - provide only required and meaningful fields:
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
// OAuth account
|
|
217
|
-
await AccountModel.save([
|
|
218
|
-
{
|
|
219
|
-
id: `acc_${Date.now()}`,
|
|
220
|
-
account_id: "google_123",
|
|
221
|
-
provider_id: "google",
|
|
222
|
-
user_id: userId,
|
|
223
|
-
access_token: "token_123",
|
|
224
|
-
},
|
|
225
|
-
]);
|
|
226
|
-
|
|
227
|
-
// Credential account
|
|
228
|
-
await AccountModel.save([
|
|
229
|
-
{
|
|
230
|
-
id: `acc_${Date.now()}`,
|
|
231
|
-
account_id: "email_account",
|
|
232
|
-
provider_id: "credential",
|
|
233
|
-
user_id: userId,
|
|
234
|
-
password: "hashed_password",
|
|
235
|
-
},
|
|
236
|
-
]);
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Principles:
|
|
240
|
-
|
|
241
|
-
- Provide only fields appropriate to each provider type
|
|
242
|
-
- Provide nullable fields only when needed for the test
|
|
243
|
-
- Do not provide dbDefault fields (created_at, updated_at)
|
|
244
|
-
|
|
245
|
-
### 7. Updating test-helpers Types
|
|
246
|
-
|
|
247
|
-
When User.id changes to string, all helper function types must be updated too:
|
|
248
|
-
|
|
249
|
-
Wrong types:
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
export async function createTestUser(): Promise<Number> { ... }
|
|
253
|
-
export async function createTestProjectParticipant(
|
|
254
|
-
projectId: number,
|
|
255
|
-
userId: number, // wrong
|
|
256
|
-
): Promise<number> { ... }
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
Correct types:
|
|
260
|
-
|
|
261
|
-
```typescript
|
|
262
|
-
export async function createTestUser(): Promise<string> { ... }
|
|
263
|
-
export async function createTestProjectParticipant(
|
|
264
|
-
projectId: number,
|
|
265
|
-
userId: string, // fixed
|
|
266
|
-
): Promise<number> { ... }
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
How to check:
|
|
270
|
-
|
|
271
|
-
```bash
|
|
272
|
-
# Find user-related parameters in test-helpers
|
|
273
|
-
grep -n "userId.*number" src/testing/test-helpers.ts
|
|
274
|
-
grep -n "evaluatorId.*number" src/testing/test-helpers.ts
|
|
275
|
-
grep -n "submittedById.*number" src/testing/test-helpers.ts
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### 8. Handling joinColumn in HasMany Relationships
|
|
279
|
-
|
|
280
|
-
When configuring a HasMany relationship, the column specified in joinColumn must exist in the child entity:
|
|
281
|
-
|
|
282
|
-
Wrong configuration:
|
|
283
|
-
|
|
284
|
-
```json
|
|
285
|
-
// project.entity.json
|
|
286
|
-
{
|
|
287
|
-
"props": [
|
|
288
|
-
{ "name": "files", "type": "relation", "with": "File",
|
|
289
|
-
"relationType": "HasMany", "joinColumn": "entity_id" }
|
|
290
|
-
]
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// file.entity.json - no entity_id column
|
|
294
|
-
{
|
|
295
|
-
"props": [
|
|
296
|
-
{ "name": "id", "type": "integer" },
|
|
297
|
-
{ "name": "url", "type": "string" }
|
|
298
|
-
]
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
Error:
|
|
303
|
-
|
|
304
|
-
```
|
|
305
|
-
column files.entity_id does not exist
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
Correct configuration:
|
|
309
|
-
|
|
310
|
-
```json
|
|
311
|
-
// file.entity.json - add entity_id column
|
|
312
|
-
{
|
|
313
|
-
"props": [
|
|
314
|
-
{ "name": "id", "type": "integer" },
|
|
315
|
-
{ "name": "entity_id", "type": "integer", "desc": "entity ID" },
|
|
316
|
-
{ "name": "url", "type": "string" }
|
|
317
|
-
]
|
|
318
|
-
}
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
Notes:
|
|
322
|
-
|
|
323
|
-
- joinColumn is the actual column name in the child table
|
|
324
|
-
- That column must exist in the child entity
|
|
325
|
-
- Must also be included in subsets to be queryable
|
|
326
|
-
|
|
327
|
-
### 9. Integrating better-auth Plugins
|
|
328
|
-
|
|
329
|
-
#### PluginSchema Type Mapping
|
|
330
|
-
|
|
331
|
-
better-auth plugins define schemas with the PluginSchema type. camelCase field names are automatically mapped to snake_case DB column names:
|
|
332
|
-
|
|
333
|
-
```typescript
|
|
334
|
-
// better-auth plugin schema example
|
|
335
|
-
const schema = {
|
|
336
|
-
user: {
|
|
337
|
-
fields: {
|
|
338
|
-
phoneNumber: {
|
|
339
|
-
// camelCase
|
|
340
|
-
type: "string",
|
|
341
|
-
required: false,
|
|
342
|
-
},
|
|
343
|
-
},
|
|
344
|
-
},
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
// stored as phone_number in DB (snake_case)
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
In Sonamu Entity, use the DB column name (snake_case) as-is:
|
|
351
|
-
|
|
352
|
-
```json
|
|
353
|
-
// user.entity.json
|
|
354
|
-
{
|
|
355
|
-
"props": [
|
|
356
|
-
{
|
|
357
|
-
"name": "phone_number",
|
|
358
|
-
"type": "string",
|
|
359
|
-
"nullable": true,
|
|
360
|
-
"desc": "phone number"
|
|
361
|
-
}
|
|
362
|
-
]
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
#### Plugin Categories
|
|
367
|
-
|
|
368
|
-
| Category | Plugins | Impact |
|
|
369
|
-
| -------------- | ----------------------------------------------------------- | ---------------------------------------------- |
|
|
370
|
-
| Basic auth | email/password, OAuth, magic link, email OTP, multi-session | User/Session/Account/Verification tables |
|
|
371
|
-
| User extension | username, phone number, admin, anonymous | Adds fields to User table |
|
|
372
|
-
| Security | two-factor, passkey | New tables needed (TwoFactor, Passkey) |
|
|
373
|
-
| Enterprise | organization, API key, SSO, JWT | New tables needed (Organization, Member, etc.) |
|
|
374
|
-
|
|
375
|
-
#### Classification by Schema Requirements
|
|
376
|
-
|
|
377
|
-
**Only existing table extension needed (add fields to User/Session)**: username, phone number, admin, anonymous, multi-session
|
|
378
|
-
|
|
379
|
-
**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)
|
|
380
|
-
|
|
381
|
-
#### Entity Writing Patterns
|
|
382
|
-
|
|
383
|
-
**Extending existing tables** — add plugin fields to User entity.json:
|
|
384
|
-
|
|
385
|
-
```json
|
|
386
|
-
// user.entity.json - example additional fields by plugin
|
|
387
|
-
// phone-number: phone_number(nullable), phone_number_verified(boolean, dbDefault:"false")
|
|
388
|
-
// admin: role(enum, dbDefault:"'user'"), banned(nullable), ban_reason(nullable), ban_expires(nullable)
|
|
389
|
-
// username: username(string)
|
|
390
|
-
// anonymous: is_anonymous(boolean, dbDefault:"false")
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
**Creating new tables** — For Account/TwoFactor etc.: generate with `pnpm sonamu stub entity` then add fields matching the plugin schema. Key notes:
|
|
394
|
-
|
|
395
|
-
- `id` is `string` type (32-character alphanumeric)
|
|
396
|
-
- FK (`user_id`) is also `string` type (since User.id is string)
|
|
397
|
-
- `nullable` fields must explicitly have `"nullable": true`
|
|
398
|
-
- better-auth uses camelCase → Sonamu uses snake_case
|
|
399
|
-
|
|
400
|
-
#### Migration Patterns
|
|
401
|
-
|
|
402
|
-
**Adding fields to existing tables** — add plugin fields with `alterTable`:
|
|
403
|
-
|
|
404
|
-
```typescript
|
|
405
|
-
await knex.schema.alterTable("users", (table) => {
|
|
406
|
-
table.string("phone_number", 255).nullable();
|
|
407
|
-
table.boolean("phone_number_verified").defaultTo(false);
|
|
408
|
-
table.text("role").notNullable().defaultTo("user");
|
|
409
|
-
});
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
**Creating new tables** — create without FK first, then add FK (must be separated!):
|
|
413
|
-
|
|
414
|
-
```typescript
|
|
415
|
-
// Step 1: create table (without FK)
|
|
416
|
-
await knex.schema.createTable("two_factors", (table) => {
|
|
417
|
-
table.text("id").primary();
|
|
418
|
-
table.text("user_id").notNullable(); // FK column only, no foreign()
|
|
419
|
-
table.text("secret").notNullable();
|
|
420
|
-
});
|
|
421
|
-
// Step 2: add FK
|
|
422
|
-
await knex.schema.alterTable("two_factors", (table) => {
|
|
423
|
-
table.foreign("user_id").references("users.id");
|
|
424
|
-
});
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
#### Sonamu Implementation Examples
|
|
428
|
-
|
|
429
|
-
Currently implemented plugins in Sonamu:
|
|
430
|
-
|
|
431
|
-
- phone-number plugin: User.phone_number, User.phone_number_verified
|
|
432
|
-
- two-factor plugin: TwoFactor table (id, secret, backup_codes, user_id)
|
|
433
|
-
|
|
434
|
-
Reference paths:
|
|
435
|
-
|
|
436
|
-
- Example project: `sonamu/examples/miomock/`
|
|
437
|
-
- User Entity: `examples/miomock/api/src/application/user/user.entity.json`
|
|
438
|
-
- TwoFactor Entity: `examples/miomock/api/src/application/two_factor/two_factor.entity.json`
|
|
439
|
-
|
|
440
|
-
#### Plugin Addition Steps
|
|
441
|
-
|
|
442
|
-
1. Write Entity: define fields in `{entity}.entity.json`
|
|
443
|
-
2. Generate Migration: auto-generate from Sonamu UI or write manually
|
|
444
|
-
3. Update SaveParams: make all nullable fields partial
|
|
445
|
-
4. Write Model: implement business logic
|
|
446
|
-
5. Update test-helpers: fix parameters whose types changed, such as userId
|
|
447
|
-
6. Write tests: write test cases for each provider/plugin
|
|
448
|
-
|
|
449
|
-
#### Plugin-Specific Notes
|
|
450
|
-
|
|
451
|
-
- **OAuth**: different fields used per provider. access_token/refresh_token/password all optional in SaveParams
|
|
452
|
-
- **two-factor**: backup_codes is a JSON string, secret is generated by a TOTP library
|
|
453
|
-
- **organization**: 3-table FK relationships. Migration order: Organization → Member, Invitation
|
|
454
|
-
- **passkey**: public_key is WebAuthn standard, counter is for replay prevention
|
|
455
|
-
- **SSO**: IdP metadata is automatically loaded from metadata_url
|