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,205 +0,0 @@
1
- ---
2
- name: sonamu-ai-agents
3
- description: Builds tool-using AI agents on Sonamu. Use when implementing an agent class, defining its tools, or managing per-request agent state. Covers BaseAgentClass, the @tools decorator, ToolLoopAgent, and AsyncLocalStorage state.
4
- ---
5
-
6
- # AI Agent Guide
7
-
8
- Sonamu provides a framework that wraps Vercel AI SDK's `ToolLoopAgent` to build class-based AI Agents.
9
-
10
- **Source code:** `modules/sonamu/src/ai/agents/`
11
-
12
- ---
13
-
14
- ## Structure
15
-
16
- | File | Role |
17
- | ---------- | ----------------------------------------------------------------------- |
18
- | `agent.ts` | `BaseAgentClass`, `tools` decorator |
19
- | `types.ts` | `AgentConfig`, `ToolDecoratorOptions`, `RegisteredToolDefinition`, etc. |
20
-
21
- ---
22
-
23
- ## BaseAgentClass
24
-
25
- The base class for Agents. Extend it to create a custom Agent.
26
-
27
- ```typescript
28
- import { BaseAgentClass, tools } from "sonamu/ai/agents";
29
- import { z } from "zod/v4";
30
-
31
- class MyAgentClass extends BaseAgentClass<{ count: number }> {
32
- constructor() {
33
- super("MyAgent"); // agentName (used as logger category)
34
- }
35
-
36
- @tools({
37
- description: "Adds two numbers",
38
- schema: {
39
- input: z.object({ a: z.number(), b: z.number() }),
40
- output: z.object({ result: z.number() }),
41
- },
42
- })
43
- async add(input: { a: number; b: number }) {
44
- return { result: input.a + input.b };
45
- }
46
- }
47
-
48
- export const MyAgent = new MyAgentClass();
49
- ```
50
-
51
- ### Key Features
52
-
53
- | Feature | Description |
54
- | ------------- | ------------------------------------------- |
55
- | `this.logger` | LogTape logger (agent category) |
56
- | `this.store` | AsyncLocalStorage-based state access |
57
- | `this.tools` | Registered toolset (ToolSet) |
58
- | `this.use()` | Run the Agent (ALS context + ToolLoopAgent) |
59
-
60
- ---
61
-
62
- ## @tools Decorator
63
-
64
- Registers a method as an AI tool. Define input/output using Zod v4 schema.
65
-
66
- ```typescript
67
- @tools({
68
- name?: string, // Tool name (default: "className.methodName" format)
69
- description?: string, // Description shown to the LLM
70
- schema: {
71
- input: z.ZodType, // Input schema (required)
72
- output?: z.ZodType, // Output schema (optional)
73
- },
74
- needsApproval?: boolean | function, // Whether user approval is required
75
- toModelOutput?: function, // Transform output returned to the model
76
- providerOptions?: ProviderOptions, // Provider-specific options
77
- })
78
- ```
79
-
80
- ### Automatic Name Generation Rule
81
-
82
- If `name` is omitted, it is auto-generated as `{ModelName(camelCase)}.{methodName(camelCase)}`.
83
-
84
- ```typescript
85
- class SearchAgentClass extends BaseAgentClass<...> {
86
- @tools({ ... })
87
- async findDocuments(input: ...) { ... }
88
- // → Tool name: "searchAgent.findDocuments"
89
- }
90
- ```
91
-
92
- The suffixes `Class`, `Model`, and `Frame` are automatically stripped from the class name.
93
-
94
- ---
95
-
96
- ## Running an Agent (use)
97
-
98
- Run the Agent with the `use()` method. ToolLoopAgent operates within the AsyncLocalStorage context.
99
-
100
- ```typescript
101
- import { anthropic } from "@ai-sdk/anthropic";
102
-
103
- const result = await MyAgent.use(
104
- // AgentConfig
105
- {
106
- model: anthropic("claude-sonnet-4-6"),
107
- instructions: "You are a math assistant.",
108
- toolChoice: "auto", // "auto" | "none" | "required"
109
- maxOutputTokens: 1000,
110
- temperature: 0.7,
111
- },
112
- // Initial state (stored in AsyncLocalStorage)
113
- { count: 0 },
114
- // Callback (receives Agent instance)
115
- async (agent) => {
116
- // agent is a ToolLoopAgent instance
117
- // Use Vercel AI SDK's agent API
118
- return agent;
119
- },
120
- );
121
- ```
122
-
123
- ### AgentConfig Options
124
-
125
- | Option | Type | Description |
126
- | -------------------------------------- | -------------------------------- | ------------------------------------ |
127
- | `model` | `LanguageModel` | AI SDK model (required) |
128
- | `instructions` | `string` | System prompt |
129
- | `toolChoice` | `"auto" \| "none" \| "required"` | Tool selection strategy |
130
- | `stopWhen` | `StopCondition` | Stop condition |
131
- | `activeTools` | `string[]` | List of tool names to activate |
132
- | `maxOutputTokens` | `number` | Maximum output tokens |
133
- | `temperature` | `number` | Temperature |
134
- | `topP` / `topK` | `number` | Sampling parameters |
135
- | `presencePenalty` / `frequencyPenalty` | `number` | Penalties |
136
- | `seed` | `number` | Seed for reproducibility |
137
- | `stopSequences` | `string[]` | Generation stop sequences |
138
- | `providerOptions` | `ProviderOptions` | Additional provider-specific options |
139
- | `headers` | `Record<string, string>` | Custom HTTP headers |
140
-
141
- ---
142
-
143
- ## State Management (AsyncLocalStorage)
144
-
145
- `BaseAgentClass` defines the state type with the generic `TStore`. When you pass the initial state to `use()`, it can be accessed via `this.store` during tool execution.
146
-
147
- ```typescript
148
- class StatefulAgentClass extends BaseAgentClass<{ processedItems: string[] }> {
149
- @tools({ ... })
150
- async processItem(input: { item: string }) {
151
- // Access state
152
- this.store?.processedItems.push(input.item);
153
- return { ok: true };
154
- }
155
- }
156
- ```
157
-
158
- **Note:** `this.store` is `undefined` outside of a `use()` context.
159
-
160
- ---
161
-
162
- ## Tool Isolation
163
-
164
- Tools for each Agent class are isolated per class. The `toolSet` getter filters by `def.from === this.constructor.name`.
165
-
166
- ```typescript
167
- class AgentA extends BaseAgentClass<void> {
168
- @tools({ ... }) async toolX() { ... }
169
- }
170
- class AgentB extends BaseAgentClass<void> {
171
- @tools({ ... }) async toolY() { ... }
172
- }
173
-
174
- // AgentA.tools → { contains only toolX }
175
- // AgentB.tools → { contains only toolY }
176
- ```
177
-
178
- ---
179
-
180
- ## Logging
181
-
182
- `this.logger` uses LogTape. The category is generated with `convertDomainToCategory(agentName, "agent")`.
183
-
184
- Debug logs are automatically recorded on tool execution:
185
-
186
- ```
187
- tools: {model}.{method} with args: {args}
188
- ```
189
-
190
- ---
191
-
192
- ## Related Packages
193
-
194
- - `ai`: Vercel AI SDK (`ToolLoopAgent`, `Agent`, `ToolSet`)
195
- - `@ai-sdk/provider-utils`: `tool()`, `Tool`, `ToolExecutionOptions`
196
- - `zod/v4`: Schema definitions
197
- - `@logtape/logtape`: Logging
198
-
199
- ---
200
-
201
- ## References
202
-
203
- - **Source code**: `modules/sonamu/src/ai/agents/`
204
- - **Vercel AI SDK**: https://sdk.vercel.ai/docs
205
- - **Vector search**: `sonamu-vector`
@@ -1,480 +0,0 @@
1
- ---
2
- name: sonamu-api
3
- description: Exposes Model methods as HTTP endpoints with the @api decorator. Use when adding or changing an API endpoint, choosing httpMethod/guards/clients options, or implementing a file upload. Covers @api, @upload, response subset selection, and the generated service client.
4
- ---
5
-
6
- # @api Decorator
7
-
8
- ## Basic Usage
9
-
10
- ```typescript
11
- @api({ httpMethod: "GET" })
12
- async findById(id: number): Promise<User> { }
13
- // → GET /user/findById?id=1
14
- ```
15
-
16
- ## Options
17
-
18
- | Option | Description | Default |
19
- | -------------- | ------------------------------------------------------- | ------------------- |
20
- | `httpMethod` | GET, POST, PUT, DELETE, PATCH | GET |
21
- | `clients` | Client types to generate | `["axios"]` |
22
- | `resourceName` | queryKey for TanStack Query | - |
23
- | `guards` | Authentication/authorization guards | - |
24
- | `path` | Custom path | `/{model}/{method}` |
25
- | `description` | API description (for documentation) | - |
26
- | `timeout` | Request timeout (ms) | - |
27
- | `contentType` | Response Content-Type | `application/json` |
28
- | `cacheControl` | Cache-Control header setting | - |
29
- | `compress` | Response compression setting (can disable with `false`) | - |
30
-
31
- ## clients Options
32
-
33
- | Client | Purpose |
34
- | ----------------------------- | ------------------------ |
35
- | `axios` | General API calls |
36
- | `axios-multipart` | File upload (axios) |
37
- | `tanstack-query` | Query hook for reads |
38
- | `tanstack-mutation` | Mutation hook for writes |
39
- | `tanstack-mutation-multipart` | File upload Mutation |
40
- | `window-fetch` | Browser fetch API |
41
-
42
- ## Pattern Examples
43
-
44
- ### Read API
45
-
46
- ```typescript
47
- @api({
48
- httpMethod: "GET",
49
- clients: ["axios", "tanstack-query"],
50
- resourceName: "Users",
51
- })
52
- async findMany(params: UserListParams): Promise<ListResult<User>> { }
53
- ```
54
-
55
- ### Write API
56
-
57
- ```typescript
58
- @api({
59
- httpMethod: "POST",
60
- clients: ["axios", "tanstack-mutation"],
61
- })
62
- async save(params: UserSaveParams[]): Promise<number[]> { }
63
- ```
64
-
65
- ### API Requiring Authorization
66
-
67
- ```typescript
68
- @api({ httpMethod: "POST", guards: ["admin"] })
69
- async del(ids: number[]): Promise<number> { }
70
- ```
71
-
72
- ## Context Access
73
-
74
- ```typescript
75
- import { Sonamu } from "sonamu";
76
-
77
- @api({ httpMethod: "GET", guards: ["user"] })
78
- async me(): Promise<User | null> {
79
- const { user } = Sonamu.getContext();
80
- return user ? this.findById("A", user.id) : null;
81
- }
82
- ```
83
-
84
- | Context Property | Description |
85
- | ---------------- | ------------------------------------------------------------------- |
86
- | `user` | Authenticated user (better-auth User, null if unauthenticated) |
87
- | `session` | Current session info (better-auth Session, null if unauthenticated) |
88
- | `request` | FastifyRequest |
89
- | `reply` | FastifyReply |
90
- | `headers` | HTTP request headers |
91
- | `bufferedFiles` | Buffer mode uploaded files |
92
- | `uploadedFiles` | Stream mode uploaded files |
93
- | `locale` | Request locale |
94
-
95
- ## File Upload (@upload)
96
-
97
- > **CRITICAL: `@upload` is used standalone without `@api`.**
98
- > Adding `@upload` **automatically generates** a POST endpoint and `axios-multipart`/`tanstack-mutation-multipart` clients.
99
- > Adding `@api` alongside it causes a **build error** due to `checkSingleDecorator` conflict.
100
-
101
- ```typescript
102
- // CORRECT
103
- @upload({ limits: { files: 10 }, guards: ["user"] })
104
- async upload(...): Promise<number[]> { }
105
-
106
- // WRONG — causes build error
107
- @api({ httpMethod: "POST", clients: ["axios-multipart"] })
108
- @upload({ limits: { files: 10 } })
109
- async upload(...): Promise<number[]> { }
110
- ```
111
-
112
- **`@upload` supported options** (`httpMethod`, `clients` are not supported — set automatically)
113
-
114
- | Option | Description |
115
- | -------------- | --------------------------------------------------- |
116
- | `guards` | Authentication/authorization guards |
117
- | `limits` | File count/size limits (`{ files: N }`) |
118
- | `consume` | `"buffer"` (default) or `"stream"` |
119
- | `description` | API documentation description |
120
- | `destination` | Stream mode only: storage driver key |
121
- | `keyGenerator` | Stream mode only: function to generate storage path |
122
-
123
- ### Parameter Rule: Must Wrap in a Single Object
124
-
125
- > **CRITICAL: If an `@upload` method has 2 or more parameters, they must be wrapped into a single object.**
126
- >
127
- > Using multiple primitive parameters causes a codegen bug in `services.template.ts` that generates `useUploadMutation` incorrectly.
128
-
129
- ```typescript
130
- // WRONG — codegen breaks (missing mutationFn argument)
131
- async upload(entity_type: string, entity_id: number, file_type: string)
132
-
133
- // CORRECT — wrap in a single object
134
- async upload(params: { entity_type: string; entity_id: number; file_type: string })
135
- ```
136
-
137
- Call site pattern:
138
-
139
- ```typescript
140
- uploadMutation.mutate({
141
- params: { entity_type, entity_id, file_type },
142
- files,
143
- });
144
- ```
145
-
146
- > Root cause: a `split(":")` bug in `services.template.ts` makes `useUploadMutation` drop
147
- > every primitive parameter after the first, so multiple primitives must be wrapped in one object.
148
-
149
- ### Buffer Mode (Default)
150
-
151
- ```typescript
152
- @upload({ limits: { files: 10 } })
153
- async uploadFiles(): Promise<{ files: SonamuFile[] }> {
154
- const { bufferedFiles } = Sonamu.getContext();
155
- // Access file data via bufferedFiles[].buffer
156
- }
157
- ```
158
-
159
- ### Stream Mode (Large Files)
160
-
161
- ```typescript
162
- @upload({
163
- consume: "stream",
164
- destination: "s3", // or "fs"
165
- keyGenerator: (file) => `uploads/${Date.now()}-${file.filename}`,
166
- limits: { files: 5 },
167
- })
168
- async uploadLargeFiles(): Promise<{ urls: string[] }> {
169
- const { uploadedFiles } = Sonamu.getContext();
170
- // Access stored path via uploadedFiles[].key
171
- }
172
- ```
173
-
174
- ---
175
-
176
- ## Real-world Business Logic Patterns
177
-
178
- ### Transaction with History Logging
179
-
180
- Pattern for atomically handling main data and history together when changing state:
181
-
182
- ```typescript
183
- // consultation.model.ts
184
-
185
- @api({ httpMethod: "POST", guards: ["user"] })
186
- async changeStatus(
187
- id: number,
188
- status: ConsultationStatus,
189
- memo?: string
190
- ): Promise<Consultation> {
191
- const wdb = this.getPuri("w");
192
-
193
- return wdb.transaction(async (trx) => {
194
- // 1. Update consultation
195
- await trx.ubRegister("consultations", {
196
- id,
197
- status,
198
- updated_at: new Date()
199
- });
200
- await trx.ubUpsert("consultations");
201
-
202
- // 2. Record status change history
203
- await trx.ubRegister("consultation_histories", {
204
- consultation_id: id,
205
- status,
206
- memo,
207
- created_at: new Date(),
208
- });
209
- await trx.ubUpsert("consultation_histories");
210
-
211
- // 3. Return result
212
- return this.findById("A", id);
213
- });
214
- }
215
- ```
216
-
217
- **Key points:**
218
-
219
- - Atomicity guaranteed by transaction
220
- - ubRegister + ubUpsert pattern
221
- - Return latest data after change
222
-
223
- ### Validation Logic and Business Rules
224
-
225
- Pattern for complex validation such as duplicate checks and capacity checks before registration:
226
-
227
- ```typescript
228
- @api({ httpMethod: "POST", guards: ["user"] })
229
- async enroll(
230
- courseId: number,
231
- userId: number
232
- ): Promise<Enrollment> {
233
- // 1. Prevent duplicate registration
234
- const existing = await this.findOne("A", {
235
- course_id: courseId,
236
- user_id: userId,
237
- });
238
-
239
- if (existing) {
240
- throw new Error("Already enrolled in this course");
241
- }
242
-
243
- // 2. Check capacity
244
- const course = await CourseModel.findById("A", courseId);
245
- const { total } = await this.findMany({ course_id: courseId });
246
-
247
- if (total >= course.max_students) {
248
- throw new Error("Course is at capacity");
249
- }
250
-
251
- // 3. Enroll
252
- const [id] = await this.save([{ course_id: courseId, user_id: userId }]);
253
- return this.findById("A", id);
254
- }
255
- ```
256
-
257
- **Key points:**
258
-
259
- - Step-by-step validation (duplicate → capacity)
260
- - Clear error messages
261
- - Save after validation passes
262
-
263
- ### Using Authorization Guards
264
-
265
- Access control based on user role:
266
-
267
- ```typescript
268
- // Regular user only
269
- @api({ httpMethod: "POST", guards: ["user"] })
270
- async save(spa: PostSaveParams[]): Promise<number[]> { }
271
-
272
- // Admin only
273
- @api({ httpMethod: "POST", guards: ["admin"] })
274
- async del(ids: number[]): Promise<number> { }
275
-
276
- // Using currently logged-in user info
277
- @api({ httpMethod: "GET", guards: ["user"] })
278
- async myConsultations(): Promise<ListResult<Consultation>> {
279
- const { user } = Sonamu.getContext();
280
- return this.findMany({ user_id: user!.id });
281
- }
282
- ```
283
-
284
- ### Writing API Tests
285
-
286
- Validating custom APIs in Business Logic tests:
287
-
288
- ```typescript
289
- // consultation.test.ts
290
- describe("E. Business Logic", () => {
291
- test("Status change API", async () => {
292
- const { consultationId } = await createTestConsultationWithDeps();
293
-
294
- // Call custom API
295
- const updated = await ConsultationModel.changeStatus(
296
- consultationId,
297
- "completed",
298
- "Consultation complete",
299
- );
300
-
301
- expect(updated.status).toBe("completed");
302
-
303
- // Verify history was recorded
304
- const histories = await ConsultationHistoryModel.findMany({
305
- consultation_id: consultationId,
306
- });
307
- expect(histories.rows).toHaveLength(1);
308
- });
309
-
310
- test("Enrollment validation", async () => {
311
- const courseId = 1;
312
- const userId = 1;
313
-
314
- // First enrollment succeeds
315
- await EnrollmentModel.enroll(courseId, userId);
316
-
317
- // Duplicate enrollment fails
318
- await expect(EnrollmentModel.enroll(courseId, userId)).rejects.toThrow(
319
- "Already enrolled in this course",
320
- );
321
- });
322
- });
323
- ```
324
-
325
- ---
326
-
327
- ## Conventions and Best Practices
328
-
329
- ### Error Message Pattern
330
-
331
- Use `this.modelName` and the `SD()` function for consistent error messages.
332
-
333
- **BAD: Hardcoded model name**
334
-
335
- ```typescript
336
- // findById
337
- if (!rows[0]) {
338
- throw new NotFoundException(SD("error.entityNotFound")("Department", id));
339
- }
340
-
341
- // findMany
342
- throw new BadRequestException(SD("error.unknownSearchField")(params.search));
343
- ```
344
-
345
- **GOOD: Using this.modelName**
346
-
347
- ```typescript
348
- // findById - auto-detects model name
349
- if (!rows[0]) {
350
- throw new NotFoundException(SD("notFound")(this.modelName, id));
351
- }
352
-
353
- // findMany - short and clear key
354
- throw new BadRequestException(SD("search.invalidField")(params.search));
355
- ```
356
-
357
- **Benefits:**
358
-
359
- - DRY principle: model name managed in one place
360
- - Refactoring safe: error messages auto-reflect model name changes
361
- - Short i18n keys: `notFound`, `search.invalidField` are more concise
362
-
363
- ### satisfies Keyword
364
-
365
- Use TypeScript's satisfies keyword to preserve type inference while checking types.
366
-
367
- **BAD: Loss of type inference**
368
-
369
- ```typescript
370
- const params: RoleListParams = {
371
- num: 24,
372
- page: 1,
373
- search: "id" as const,
374
- orderBy: "id-desc" as const,
375
- ...rawParams,
376
- };
377
- ```
378
-
379
- **GOOD: Type check + preserved inference with satisfies**
380
-
381
- ```typescript
382
- const params = {
383
- num: 24,
384
- page: 1,
385
- search: "id" as const,
386
- orderBy: "id-desc" as const,
387
- ...rawParams,
388
- } satisfies RoleListParams;
389
- ```
390
-
391
- **Benefits:**
392
-
393
- - Compile-time verification: checks that params satisfies the RoleListParams type
394
- - Preserved type inference: params keeps its narrowed type
395
- - Better IDE support: more accurate autocomplete and type checking
396
-
397
- ### debug Option
398
-
399
- The debug option in executeSubsetQuery defaults to false, so it does not need to be specified explicitly.
400
-
401
- **BAD: Unnecessary debug: false**
402
-
403
- ```typescript
404
- return this.executeSubsetQuery({
405
- subset,
406
- qb,
407
- params,
408
- enhancers,
409
- debug: false, // unnecessary — it's the default
410
- });
411
- ```
412
-
413
- **GOOD: Use the default**
414
-
415
- ```typescript
416
- return this.executeSubsetQuery({
417
- subset,
418
- qb,
419
- params,
420
- enhancers,
421
- });
422
- ```
423
-
424
- **When to use debug: true:**
425
-
426
- ```typescript
427
- // Only specify when debugging
428
- return this.executeSubsetQuery({
429
- subset,
430
- qb,
431
- params,
432
- debug: true, // Print SQL query log
433
- });
434
- ```
435
-
436
- ## @stream Decorator (SSE)
437
-
438
- Creates a Server-Sent Events endpoint.
439
-
440
- ```typescript
441
- import { stream } from "sonamu";
442
- import { z } from "zod";
443
-
444
- @stream({
445
- type: "sse",
446
- events: z.object({
447
- progress: z.object({ percent: z.number() }),
448
- done: z.object({ result: z.string() }),
449
- }),
450
- guards: ["user"],
451
- })
452
- async processStream() { ... }
453
- ```
454
-
455
- | Option | Description | Required |
456
- | -------------- | ---------------------------------------------- | -------- |
457
- | `type` | `"sse"` (only SSE currently supported) | Yes |
458
- | `events` | Define event keys and payloads with Zod schema | Yes |
459
- | `path` | Custom path | - |
460
- | `resourceName` | Resource name | - |
461
- | `guards` | Authentication/authorization guards | - |
462
-
463
- ## @transactional Decorator
464
-
465
- Wraps the entire method in an automatic transaction. Reuses an existing transaction context if one is already active.
466
-
467
- ```typescript
468
- import { transactional } from "sonamu";
469
-
470
- @transactional({ isolation: "serializable" })
471
- async transferFunds(fromId: number, toId: number, amount: number) {
472
- // this.getPuri("w") automatically runs inside the transaction
473
- }
474
- ```
475
-
476
- | Option | Description | Default |
477
- | ----------- | ------------------------------------------------------------------------------------------ | ------- |
478
- | `isolation` | Transaction isolation level (read uncommitted/read committed/repeatable read/serializable) | - |
479
- | `readOnly` | Read-only transaction | `false` |
480
- | `dbPreset` | DB preset | `"w"` |