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.
Files changed (56) hide show
  1. package/dist/bin/cli.js +10 -253
  2. package/dist/migration/code-generation.js +2 -2
  3. package/dist/ui-web/assets/{index-D0MHYbxl.js → index-CJf8uJYf.js} +47 -41
  4. package/dist/ui-web/assets/index-GMMIVGja.css +1 -0
  5. package/dist/ui-web/index.html +2 -2
  6. package/package.json +3 -3
  7. package/src/bin/cli.ts +12 -375
  8. package/src/migration/code-generation.ts +1 -1
  9. package/dist/ui-web/assets/index-Dx_JX4aQ.css +0 -1
  10. package/src/skills/AGENTS.md +0 -108
  11. package/src/skills/sonamu/SKILL.md +0 -75
  12. package/src/skills/sonamu-ai-agents/SKILL.md +0 -205
  13. package/src/skills/sonamu-api/SKILL.md +0 -480
  14. package/src/skills/sonamu-auth/SKILL.md +0 -327
  15. package/src/skills/sonamu-auth/references/plugins.md +0 -310
  16. package/src/skills/sonamu-auth/references/user-id-migration-followups.md +0 -192
  17. package/src/skills/sonamu-auth/references/user-id-migration.md +0 -455
  18. package/src/skills/sonamu-config/SKILL.md +0 -203
  19. package/src/skills/sonamu-config/references/database.md +0 -452
  20. package/src/skills/sonamu-config/references/environments.md +0 -178
  21. package/src/skills/sonamu-config/references/server-options.md +0 -400
  22. package/src/skills/sonamu-entity/SKILL.md +0 -180
  23. package/src/skills/sonamu-entity/references/creation-workflow.md +0 -581
  24. package/src/skills/sonamu-entity/references/design-guides.md +0 -243
  25. package/src/skills/sonamu-entity/references/field-types.md +0 -170
  26. package/src/skills/sonamu-entity/references/relations-detail.md +0 -245
  27. package/src/skills/sonamu-entity/references/relations.md +0 -463
  28. package/src/skills/sonamu-entity/references/subset.md +0 -156
  29. package/src/skills/sonamu-fixture/SKILL.md +0 -180
  30. package/src/skills/sonamu-fixture/references/cli-usage.md +0 -439
  31. package/src/skills/sonamu-fixture/references/cone.md +0 -298
  32. package/src/skills/sonamu-frontend/SKILL.md +0 -142
  33. package/src/skills/sonamu-frontend/references/components.md +0 -323
  34. package/src/skills/sonamu-frontend/references/examples.md +0 -64
  35. package/src/skills/sonamu-frontend/references/hooks.md +0 -273
  36. package/src/skills/sonamu-frontend/references/runtime.md +0 -165
  37. package/src/skills/sonamu-frontend/references/scaffolding.md +0 -439
  38. package/src/skills/sonamu-i18n/SKILL.md +0 -287
  39. package/src/skills/sonamu-migration/SKILL.md +0 -316
  40. package/src/skills/sonamu-naite/SKILL.md +0 -266
  41. package/src/skills/sonamu-query/SKILL.md +0 -48
  42. package/src/skills/sonamu-query/references/model-patterns.md +0 -390
  43. package/src/skills/sonamu-query/references/model.md +0 -366
  44. package/src/skills/sonamu-query/references/puri.md +0 -413
  45. package/src/skills/sonamu-query/references/search.md +0 -238
  46. package/src/skills/sonamu-query/references/upsert.md +0 -324
  47. package/src/skills/sonamu-tasks/SKILL.md +0 -236
  48. package/src/skills/sonamu-testing/SKILL.md +0 -251
  49. package/src/skills/sonamu-testing/references/devrunner.md +0 -405
  50. package/src/skills/sonamu-testing/references/helpers.md +0 -185
  51. package/src/skills/sonamu-testing/references/patterns.md +0 -263
  52. package/src/skills/sonamu-testing/references/pitfalls.md +0 -588
  53. package/src/skills/sonamu-testing/references/quick-start.md +0 -285
  54. package/src/skills/sonamu-testing/references/type-safety.md +0 -172
  55. package/src/skills/sonamu-testing/references/writing-plan.md +0 -375
  56. 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