sonamu 0.10.7 → 0.10.9

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 (79) hide show
  1. package/dist/api/decorators.d.ts.map +1 -1
  2. package/dist/api/decorators.js +5 -12
  3. package/dist/bin/cli.js +10 -253
  4. package/dist/database/base-model.d.ts +1 -0
  5. package/dist/database/base-model.d.ts.map +1 -1
  6. package/dist/database/base-model.js +14 -8
  7. package/dist/database/db.d.ts +2 -1
  8. package/dist/database/db.d.ts.map +1 -1
  9. package/dist/database/db.js +13 -3
  10. package/dist/database/puri-wrapper.d.ts.map +1 -1
  11. package/dist/database/puri-wrapper.js +4 -11
  12. package/dist/database/transaction-context.d.ts +8 -3
  13. package/dist/database/transaction-context.d.ts.map +1 -1
  14. package/dist/database/transaction-context.js +12 -7
  15. package/dist/migration/code-generation.js +2 -2
  16. package/dist/naite/naite-reporter.d.ts +8 -3
  17. package/dist/naite/naite-reporter.d.ts.map +1 -1
  18. package/dist/naite/naite-reporter.js +9 -4
  19. package/dist/ui-web/assets/{index-D0MHYbxl.js → index-CJf8uJYf.js} +47 -41
  20. package/dist/ui-web/assets/index-GMMIVGja.css +1 -0
  21. package/dist/ui-web/index.html +2 -2
  22. package/package.json +3 -3
  23. package/src/api/decorators.ts +11 -22
  24. package/src/bin/cli.ts +12 -375
  25. package/src/database/__tests__/transaction-scope.test.ts +227 -0
  26. package/src/database/base-model.ts +31 -6
  27. package/src/database/db.ts +17 -2
  28. package/src/database/puri-wrapper.ts +5 -22
  29. package/src/database/transaction-context.ts +14 -6
  30. package/src/migration/code-generation.ts +1 -1
  31. package/src/naite/naite-reporter.ts +8 -3
  32. package/dist/ui-web/assets/index-Dx_JX4aQ.css +0 -1
  33. package/src/skills/AGENTS.md +0 -108
  34. package/src/skills/sonamu/SKILL.md +0 -75
  35. package/src/skills/sonamu-ai-agents/SKILL.md +0 -205
  36. package/src/skills/sonamu-api/SKILL.md +0 -480
  37. package/src/skills/sonamu-auth/SKILL.md +0 -327
  38. package/src/skills/sonamu-auth/references/plugins.md +0 -310
  39. package/src/skills/sonamu-auth/references/user-id-migration-followups.md +0 -192
  40. package/src/skills/sonamu-auth/references/user-id-migration.md +0 -455
  41. package/src/skills/sonamu-config/SKILL.md +0 -203
  42. package/src/skills/sonamu-config/references/database.md +0 -452
  43. package/src/skills/sonamu-config/references/environments.md +0 -178
  44. package/src/skills/sonamu-config/references/server-options.md +0 -400
  45. package/src/skills/sonamu-entity/SKILL.md +0 -180
  46. package/src/skills/sonamu-entity/references/creation-workflow.md +0 -581
  47. package/src/skills/sonamu-entity/references/design-guides.md +0 -243
  48. package/src/skills/sonamu-entity/references/field-types.md +0 -170
  49. package/src/skills/sonamu-entity/references/relations-detail.md +0 -245
  50. package/src/skills/sonamu-entity/references/relations.md +0 -463
  51. package/src/skills/sonamu-entity/references/subset.md +0 -156
  52. package/src/skills/sonamu-fixture/SKILL.md +0 -180
  53. package/src/skills/sonamu-fixture/references/cli-usage.md +0 -439
  54. package/src/skills/sonamu-fixture/references/cone.md +0 -298
  55. package/src/skills/sonamu-frontend/SKILL.md +0 -142
  56. package/src/skills/sonamu-frontend/references/components.md +0 -323
  57. package/src/skills/sonamu-frontend/references/examples.md +0 -64
  58. package/src/skills/sonamu-frontend/references/hooks.md +0 -273
  59. package/src/skills/sonamu-frontend/references/runtime.md +0 -165
  60. package/src/skills/sonamu-frontend/references/scaffolding.md +0 -439
  61. package/src/skills/sonamu-i18n/SKILL.md +0 -287
  62. package/src/skills/sonamu-migration/SKILL.md +0 -316
  63. package/src/skills/sonamu-naite/SKILL.md +0 -266
  64. package/src/skills/sonamu-query/SKILL.md +0 -48
  65. package/src/skills/sonamu-query/references/model-patterns.md +0 -390
  66. package/src/skills/sonamu-query/references/model.md +0 -366
  67. package/src/skills/sonamu-query/references/puri.md +0 -413
  68. package/src/skills/sonamu-query/references/search.md +0 -238
  69. package/src/skills/sonamu-query/references/upsert.md +0 -324
  70. package/src/skills/sonamu-tasks/SKILL.md +0 -236
  71. package/src/skills/sonamu-testing/SKILL.md +0 -251
  72. package/src/skills/sonamu-testing/references/devrunner.md +0 -405
  73. package/src/skills/sonamu-testing/references/helpers.md +0 -185
  74. package/src/skills/sonamu-testing/references/patterns.md +0 -263
  75. package/src/skills/sonamu-testing/references/pitfalls.md +0 -588
  76. package/src/skills/sonamu-testing/references/quick-start.md +0 -285
  77. package/src/skills/sonamu-testing/references/type-safety.md +0 -172
  78. package/src/skills/sonamu-testing/references/writing-plan.md +0 -375
  79. package/src/skills/sonamu-vector/SKILL.md +0 -222
@@ -1,463 +0,0 @@
1
- # Relationships
2
-
3
- ## Relationship Selection Guide
4
-
5
- ### 1:N vs N:M Decision Criteria
6
-
7
- | Question | 1:N (BelongsToOne) | N:M (ManyToMany or intermediate entity) |
8
- | -------------------------------------------------- | ------------------ | --------------------------------------- |
9
- | Can one A belong to multiple Bs? | No | Yes |
10
- | Does the relationship need additional information? | No | Yes → intermediate entity |
11
- | Can it be expressed as "A belongs to B"? | Yes | No |
12
-
13
- **Examples:**
14
-
15
- - Post → Author: 1:N (a post has one author)
16
- - Post ↔ Tag: N:M (multiple posts have multiple tags)
17
- - Researcher ↔ Task: N:M + intermediate entity (participation rate, role, and other additional info)
18
-
19
- ### When an Intermediate Entity Is Needed
20
-
21
- If an N:M relationship has **additional information**, use an **intermediate entity** instead of ManyToMany.
22
-
23
- | Situation | ManyToMany | Intermediate entity |
24
- | -------------------------------------- | ---------- | ------------------- |
25
- | Only a simple connection is needed | ✓ | |
26
- | Relationship needs a date/period | | ✓ |
27
- | Relationship needs a role/status | | ✓ |
28
- | Relationship needs a quantity/ratio | | ✓ |
29
- | Relationship history management needed | | ✓ |
30
-
31
- **Intermediate entity example:**
32
-
33
- ```
34
- Researcher ↔ Task
35
- └─ ProjectResearcher (intermediate entity)
36
- - researcher: BelongsToOne → User
37
- - task: BelongsToOne → Task
38
- - role: enum (lead/participant)
39
- - participation_rate: integer
40
- - begin_at, end_at: date
41
- ```
42
-
43
- ### Joint Ownership / Joint Achievement Pattern
44
-
45
- When multiple people are connected to a single result:
46
-
47
- ```
48
- Achievement ↔ Researcher
49
- └─ AchievementParticipant (intermediate entity)
50
- - achievement: BelongsToOne → Achievement
51
- - researcher: BelongsToOne → User
52
- - is_primary: boolean (whether they are the original registrant)
53
- - contribution_rate: integer (optional)
54
- ```
55
-
56
- **Key point**: The achievement is registered once, and participants are linked to it (prevents duplication)
57
-
58
- ### Status History Pattern
59
-
60
- When status change history needs to be managed:
61
-
62
- ```
63
- Application (ApplyDeliberation)
64
- └─ ApplicationHistory (ApplyDeliberationHistory)
65
- - apply_deliberation: BelongsToOne → ApplyDeliberation
66
- - status: enum (previous status or changed status)
67
- - changed_at: date
68
- - changed_by: BelongsToOne → User
69
- - reason: string
70
- ```
71
-
72
- ### Change Request Pattern
73
-
74
- When data changes require an approval process:
75
-
76
- ```
77
- Task
78
- └─ TaskChangeRequest
79
- - task: BelongsToOne → Task
80
- - status: enum (requested/approved/rejected)
81
- - reason: string
82
- - requested_by: BelongsToOne → User
83
- - requested_at: date
84
- - approved_by: BelongsToOne → User (nullable)
85
- - approved_at: date (nullable)
86
-
87
- └─ TaskChangeHistory
88
- - change_request: BelongsToOne → TaskChangeRequest
89
- - change_type: enum (add/delete/update)
90
- - target_user: BelongsToOne → User (subject of change)
91
- - before_value: json (before change, optional)
92
- - after_value: json (after change, optional)
93
- ```
94
-
95
- ---
96
-
97
- ## Common Domain Patterns
98
-
99
- ### Organizational Structure (Institution-Department-User)
100
-
101
- ```
102
- Institution
103
- └─ departments: HasMany → Department
104
-
105
- Department
106
- └─ institution: BelongsToOne → Institution
107
- └─ employees: HasMany → User
108
-
109
- User
110
- └─ institution: BelongsToOne → Institution
111
- └─ department: BelongsToOne → Department
112
- ```
113
-
114
- ### Project Participation (Project-Participant)
115
-
116
- ```
117
- Project
118
- └─ participants: HasMany → ProjectParticipant
119
- └─ owner: BelongsToOne → User
120
-
121
- ProjectParticipant [intermediate entity]
122
- └─ project: BelongsToOne → Project
123
- └─ user: BelongsToOne → User
124
- └─ role: enum
125
- └─ participation_rate: integer
126
- └─ begin_at, end_at: date
127
- ```
128
-
129
- ### Committee-Member
130
-
131
- ```
132
- Committee
133
- └─ members: HasMany → CommitteeMember
134
-
135
- CommitteeMember [intermediate entity]
136
- └─ committee: BelongsToOne → Committee
137
- └─ user: BelongsToOne → User
138
- └─ member_type: enum (internal/external)
139
- └─ participate_year: string
140
- ```
141
-
142
- ### Review/Evaluation (Target-Reviewer-Result)
143
-
144
- ```
145
- EvaluationTarget
146
- └─ committee: BelongsToOne → Committee
147
- └─ target_entity: BelongsToOne → Task (or polymorphic)
148
-
149
- EvaluationResult
150
- └─ target: BelongsToOne → EvaluationTarget
151
- └─ evaluator: BelongsToOne → CommitteeMember
152
- └─ score: integer or enum (approved/rejected)
153
- └─ opinion: string
154
- ```
155
-
156
- ### Step-by-Step Data Flow (Application → Confirmation)
157
-
158
- When data moves through stages:
159
-
160
- ```
161
- ApplyDeliberation
162
- └─ task: OneToOne → Task (references the task created on approval)
163
-
164
- Task
165
- └─ apply_deliberation: BelongsToOne → ApplyDeliberation (references the original application)
166
- ```
167
-
168
- **Key point**: Bidirectional references allow querying from either side
169
-
170
- ---
171
-
172
- ## Which Relationship to Use?
173
-
174
- | Situation | Relationship type | Example |
175
- | -------------------------- | ----------------- | -------------------- |
176
- | "A belongs to B" (N:1) | `BelongsToOne` | Post → User (author) |
177
- | "A has many Bs" (1:N) | `HasMany` | User → Posts |
178
- | "A and B are 1:1" | `OneToOne` | User ↔ Employee |
179
- | "A and B are many-to-many" | `ManyToMany` | Post ↔ Tag |
180
-
181
- ## BelongsToOne (N:1) - Most Common
182
-
183
- **Situation**: When a Post belongs to a User (author)
184
-
185
- ```json
186
- {
187
- "type": "relation",
188
- "name": "author",
189
- "with": "User",
190
- "relationType": "BelongsToOne",
191
- "nullable": true,
192
- "onUpdate": "CASCADE",
193
- "onDelete": "SET NULL",
194
- "desc": "author"
195
- }
196
- ```
197
-
198
- **Auto-generated**: `author_id` column (FK)
199
-
200
- **Note**: Do not define `author_id` directly in props (it is auto-generated)
201
-
202
- **Optional options:**
203
-
204
- - `customJoinClause`: custom JOIN condition SQL (specify JOIN condition directly instead of FK)
205
- - `useConstraint`: whether to create FK constraint (default: `true`). If `false`, the FK column is created but no DB constraint is generated
206
-
207
- ### Using FK in Code
208
-
209
- Since a BelongsToOne relationship automatically creates a `{name}_id` column, use the correct field name when working with it directly in Model, FixtureGenerator, etc.
210
-
211
- **Correct pattern:**
212
-
213
- ```typescript
214
- // Entity definition
215
- {
216
- "type": "relation",
217
- "name": "company",
218
- "with": "Company",
219
- "relationType": "BelongsToOne"
220
- }
221
-
222
- // In Model save() or FixtureGenerator
223
- const department = {
224
- name: "Engineering",
225
- company_id: 1 // ✓ CORRECT: {relation_name}_id form
226
- };
227
-
228
- await puri.ubRegister("departments", department);
229
- ```
230
-
231
- **Wrong pattern (common mistake):**
232
-
233
- ```typescript
234
- // ✗ WRONG: using relation name directly
235
- const department = {
236
- name: "Engineering",
237
- company: 1, // FK is not set! company_id is saved as NULL
238
- };
239
-
240
- // ✗ WRONG: passing as object
241
- const department = {
242
- name: "Engineering",
243
- company: { id: 1 }, // FK is not set!
244
- };
245
- ```
246
-
247
- **FixtureGenerator example:**
248
-
249
- ```typescript
250
- // inside fixture-generator.ts
251
- if (isBelongsToOneRelationProp(prop) || (isOneToOneRelationProp(prop) && prop.hasJoinColumn)) {
252
- const relationValue = await this.generateRelationValue(entity, prop, context);
253
-
254
- // ✓ CORRECT: set FK as {prop.name}_id
255
- fixture[`${prop.name}_id`] = relationValue;
256
- } else {
257
- fixture[prop.name] = relationValue;
258
- }
259
- ```
260
-
261
- **Key points:**
262
-
263
- - Entity JSON: `"name": "company"` (relation name)
264
- - DB column: `company_id` (auto-generated)
265
- - TypeScript code: use `company_id` (setting FK)
266
- - Entity subset: `"company.id"` form (FieldExpr)
267
-
268
- ## HasMany (1:N) - For Reverse Lookup
269
-
270
- **Situation**: When you want to query a User's Posts
271
-
272
- ```json
273
- {
274
- "type": "relation",
275
- "name": "posts",
276
- "with": "Post",
277
- "relationType": "HasMany",
278
- "joinColumn": "author_id",
279
- "desc": "authored posts"
280
- }
281
- ```
282
-
283
- **Required**: `joinColumn` = FK column name in the related table
284
-
285
- **Optional**: `fromColumn` = matching column in your own table (default: `id`). Use when JOIN needs to use a non-standard PK
286
-
287
- **Important**: If the `joinColumn` field is not defined, a Zod schema validation error will occur.
288
-
289
- **No DB column is created** (virtual)
290
-
291
- **When is it needed?**
292
-
293
- - When reverse lookup like `user.posts.title` is needed in a Subset
294
- - Can be omitted if not needed
295
-
296
- ### HasMany Performance Optimization
297
-
298
- HasMany relationships are automatically optimized using the **DataLoader pattern**:
299
-
300
- - Parent record IDs are collected in batches
301
- - All child records are queried in a single `whereIn` query
302
- - **No N+1 query problem**
303
-
304
- This optimization is applied automatically and requires no additional configuration.
305
-
306
- **Implementation location**: `processLoaders` method in `modules/sonamu/src/database/base-model.ts`
307
-
308
- ## OneToOne (1:1)
309
-
310
- **Situation**: When User and Employee are 1:1
311
-
312
- **The side holding the FK** (Employee):
313
-
314
- ```json
315
- {
316
- "type": "relation",
317
- "name": "user",
318
- "with": "User",
319
- "relationType": "OneToOne",
320
- "hasJoinColumn": true,
321
- "onUpdate": "CASCADE",
322
- "onDelete": "CASCADE",
323
- "desc": "user"
324
- }
325
- ```
326
-
327
- **The side without the FK** (User):
328
-
329
- ```json
330
- {
331
- "type": "relation",
332
- "name": "employee",
333
- "with": "Employee",
334
- "relationType": "OneToOne",
335
- "nullable": true,
336
- "desc": "employee info"
337
- }
338
- ```
339
-
340
- **Key point**: FK is only created on the side with `hasJoinColumn: true` (omitting it means no FK; it is an optional option)
341
-
342
- **Optional options (when `hasJoinColumn: true`):**
343
-
344
- - `customJoinClause`: custom JOIN condition SQL
345
- - `useConstraint`: whether to create FK constraint (default: `true`)
346
-
347
- ## ManyToMany (N:M)
348
-
349
- **Situation**: When Post and Tag are many-to-many
350
-
351
- ```json
352
- {
353
- "type": "relation",
354
- "name": "tags",
355
- "with": "Tag",
356
- "relationType": "ManyToMany",
357
- "joinTable": "posts__tags",
358
- "onUpdate": "CASCADE",
359
- "onDelete": "CASCADE",
360
- "desc": "tags"
361
- }
362
- ```
363
-
364
- **Required**: `joinTable`, `onUpdate`, `onDelete`
365
-
366
- ### ManyToMany Naming Conventions
367
-
368
- **joinTable (join table name)**: use **double** underscore
369
-
370
- ```
371
- User ↔ Role → user__roles
372
- Post ↔ Tag → posts__tags (alphabetical order recommended)
373
- ```
374
-
375
- **joinColumn (join table column name)**: use **single** underscore
376
-
377
- ```
378
- user__roles table:
379
- - user_id (single underscore)
380
- - role_id (single underscore)
381
- ```
382
-
383
- **Example**:
384
-
385
- ```typescript
386
- // Entity: User
387
- {
388
- "name": "roles",
389
- "relationType": "ManyToMany",
390
- "with": "Role",
391
- "joinTable": "user__roles" // double underscore
392
- }
393
-
394
- // In Model save():
395
- puri.ubRegister("user__roles", {
396
- user_id, // single underscore
397
- role_id // single underscore
398
- });
399
- ```
400
-
401
- ## Self-Reference
402
-
403
- **Situation**: When an Employee's manager is also an Employee
404
-
405
- ```json
406
- {
407
- "type": "relation",
408
- "name": "manager",
409
- "with": "Employee",
410
- "relationType": "BelongsToOne",
411
- "nullable": true,
412
- "onUpdate": "CASCADE",
413
- "onDelete": "SET NULL",
414
- "desc": "direct manager"
415
- }
416
- ```
417
-
418
- **Required**: `nullable: true` (top-level has no manager)
419
-
420
- ## IMPORTANT: parentId and Parent Subset HasMany Cannot Be Used Together
421
-
422
- ### Problem
423
-
424
- When parentId is set, the **FK column is removed from the child entity's BaseSchema**.
425
- In this state, if the parent's subset includes the child via HasMany, the SSO LoaderQuery
426
- executes `whereIn("child.parent_fk", fromIds)`, but since the FK is missing, a TypeScript error occurs.
427
-
428
- ```
429
- Error: '{child_table}.{parent_fk}' is not assignable to type 'AvailableColumns'
430
- ```
431
-
432
- ### Solution: Choose One of the Two
433
-
434
- | Requirement | Choice | parentId | HasMany in parent subset |
435
- | ----------------------------------------------- | ------------------ | ---------- | ------------------------ |
436
- | Query child list together in parent detail view | Independent entity | ✗ not used | ✓ possible |
437
- | Child is CRUD'd only through parent | Use parentId | ✓ used | ✗ not possible |
438
-
439
- ### Decision Criteria
440
-
441
- | Question | Yes → Independent entity | No → parentId |
442
- | ----------------------------------------------------------- | ------------------------ | ------------- |
443
- | Will the child ever be queried/modified standalone? | ✓ | |
444
- | Does the admin screen need a separate child list page? | ✓ | |
445
- | Is the child list queried as a subset in the parent detail? | ✓ | |
446
-
447
- ### Example
448
-
449
- ```json
450
- // DO NOT - Incorrect (causes error)
451
- // entity: ApplyDeliberationResearcher
452
- { "parentId": "apply_deliberation_id" } // FK removed from BaseSchema
453
-
454
- // entity: ApplyDeliberation subset
455
- { "A": ["*", { "researchers": ["*"] }] } // SSO LoaderQuery error
456
-
457
- // DO - Correct (change to independent entity, remove parentId)
458
- // entity: ApplyDeliberationResearcher - no parentId, FK is preserved in BaseSchema
459
- // entity: ApplyDeliberation subset
460
- { "A": ["*", { "researchers": ["*"] }] } // works correctly
461
- ```
462
-
463
- ---
@@ -1,156 +0,0 @@
1
- # Subset — Response Field Scope
2
-
3
- ## Basic Structure
4
-
5
- ```json
6
- {
7
- "subsets": {
8
- "A": ["id", "created_at", "username", "email", "role"],
9
- "P": ["id", "username", "employee.department.name"],
10
- "SS": ["id", "username"]
11
- }
12
- }
13
- ```
14
-
15
- ## Naming Conventions
16
-
17
- **WARNING: Only use A, P, and SS as Subset names. Do not use arbitrary names like S, D, or L.**
18
-
19
- | Subset | Purpose |
20
- | ---------- | ------------------------------------------------------------------------------ |
21
- | `A` | All - all fields (detail view, admin). **Required** |
22
- | `P` | Partial/Profile - partial fields including relations (for list views) |
23
- | `SS` | Super Simple/Summary - minimal fields, just ID + name (for dropdowns, selects) |
24
- | `P2`, `P3` | Additional profiles (only for special cases) |
25
-
26
- ### IMPORTANT: Subset A Must Include All Fields
27
-
28
- **Subset A must include all regular fields and major relation fields of the Entity.**
29
-
30
- **DO:**
31
-
32
- ```json
33
- {
34
- "props": [
35
- { "name": "id", "type": "integer" },
36
- { "name": "created_at", "type": "date" },
37
- { "name": "title", "type": "string" },
38
- { "name": "status", "type": "enum", "id": "Status" },
39
- { "type": "relation", "name": "author", "with": "User", "relationType": "BelongsToOne" }
40
- ],
41
- "subsets": {
42
- "A": ["id", "created_at", "title", "status", "author.id", "author.name"]
43
- }
44
- }
45
- ```
46
-
47
- **DO NOT:**
48
-
49
- ```json
50
- {
51
- "subsets": {
52
- "A": ["id", "title"] // created_at, status, author omitted - incorrect
53
- }
54
- }
55
- ```
56
-
57
- **Rules:**
58
-
59
- - Include all regular fields (id, created_at, business fields, etc.)
60
- - For BelongsToOne relations, include at least `.id` and a display field (`.name`, `.title`, etc.)
61
- - HasMany relations are optional (include only when needed)
62
-
63
- ### When Only a Single Subset Is Needed
64
-
65
- ```json
66
- // DO - Correct: create only A
67
- { "subsets": { "A": ["id", "name", "created_at"] } }
68
- ```
69
-
70
- ### DO NOT - Incorrect Subset Names
71
-
72
- ```json
73
- // Incorrect: do not use S, D, L, etc.
74
- {
75
- "subsets": {
76
- "A": [...],
77
- "S": [...], // NEVER - use SS instead
78
- "D": [...], // NEVER - do not use
79
- "L": [...] // NEVER - use P instead
80
- }
81
- }
82
- ```
83
-
84
- ## Relation Fields (Dot Notation)
85
-
86
- ```json
87
- {
88
- "subsets": {
89
- "P": ["id", "username", "employee.salary", "employee.department.name"]
90
- }
91
- }
92
- ```
93
-
94
- - BelongsToOne/OneToOne: automatic LEFT JOIN
95
- - HasMany/ManyToMany: automatically optimized with DataLoader
96
-
97
- ## ID-Only Reference Optimization
98
-
99
- ```json
100
- { "SS": ["id", "title", "user.id"] }
101
- ```
102
-
103
- - Referencing only `user.id` reads the `user_id` column directly without a JOIN
104
-
105
- ## Internal Fields
106
-
107
- ```json
108
- {
109
- "subsets": {
110
- "A": ["id", "username", { "field": "password_hash", "internal": true }]
111
- }
112
- }
113
- ```
114
-
115
- - Included in the query but excluded from the API response type
116
-
117
- ## Usage in Models
118
-
119
- ```typescript
120
- // findById
121
- const user = await UserModel.findById("P", 1);
122
-
123
- // findMany
124
- const { rows } = await UserModel.findMany("P", { num: 20, page: 1 });
125
-
126
- // getSubsetQueries + executeSubsetQuery
127
- const { qb } = UserModel.getSubsetQueries("P");
128
- qb.where("users.role", "admin");
129
-
130
- const result = await UserModel.executeSubsetQuery({
131
- subset: "P",
132
- qb,
133
- params: { num: 20, page: 1 },
134
- });
135
- ```
136
-
137
- ## Notes
138
-
139
- - The base Subset `A` is required
140
- - Nesting depth of 3 or fewer levels is recommended
141
- - Exclude unnecessary relations from list-purpose Subsets
142
- - **Use relation notation for FK columns**: FK columns for BelongsToOne relations (e.g. `user_id`) must be accessed in Subsets using the `user.id` form. This is because Sonamu recognizes the relation notation and automatically optimizes by reading the FK column directly when only `.id` is referenced.
143
-
144
- ```json
145
- // DO NOT - Incorrect: using FK column directly
146
- { "A": ["id", "user_id", "title"] }
147
-
148
- // DO - Correct: use relation.field format (auto-optimized)
149
- { "A": ["id", "user.id", "title"] }
150
- // → Sonamu reads the user_id column directly (no JOIN)
151
- ```
152
-
153
- **Working code references:**
154
-
155
- - `sonamu/examples/miomock/api/src/application/project/project.entity.json` - Subset definition examples
156
- - `sonamu/examples/miomock/api/src/application/employee/employee.entity.json` - BelongsToOne relation examples