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,366 +0,0 @@
1
- # Model — Structure, CRUD, Subset Queries
2
-
3
- ## Basic Structure
4
-
5
- ```typescript
6
- import { api, BaseModelClass, ListResult, NotFoundException } from "sonamu";
7
- import type { UserSubsetKey, UserSubsetMapping } from "../sonamu.generated";
8
- import { userLoaderQueries, userSubsetQueries } from "../sonamu.generated.sso";
9
- import type { UserListParams, UserSaveParams } from "./user.types";
10
-
11
- class UserModelClass extends BaseModelClass<
12
- UserSubsetKey,
13
- UserSubsetMapping,
14
- typeof userSubsetQueries,
15
- typeof userLoaderQueries
16
- > {
17
- constructor() {
18
- super("User", userSubsetQueries, userLoaderQueries);
19
- }
20
- }
21
-
22
- export const UserModel = new UserModelClass();
23
- ```
24
-
25
- ## CRUD Pattern
26
-
27
- Sonamu Model provides the following basic methods:
28
-
29
- | Method | Purpose | Notes |
30
- | ---------- | ---------------------- | ------------------ |
31
- | `findById` | Retrieve single record | |
32
- | `findMany` | Retrieve list | |
33
- | `save` | Create/update | upsert behavior |
34
- | `del` | Delete | Note: not `delete` |
35
-
36
- **Avoiding JavaScript reserved words:** `delete` is a JS reserved word, so it is named `del`. While TypeScript allows `delete` as a method name without a compile error, it can cause runtime issues, so Sonamu uses `del`.
37
-
38
- ### findById
39
-
40
- ```typescript
41
- @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "User" })
42
- async findById<T extends UserSubsetKey>(subset: T, id: number): Promise<UserSubsetMapping[T]> {
43
- const { rows } = await this.findMany(subset, { id, num: 1, page: 1 });
44
- if (!rows[0]) throw new NotFoundException(`User ID ${id} not found`);
45
- return rows[0];
46
- }
47
- ```
48
-
49
- ### findMany
50
-
51
- ```typescript
52
- @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "Users" })
53
- async findMany<T extends UserSubsetKey>(
54
- subset: T,
55
- params: UserListParams = { num: 10, page: 1 }
56
- ): Promise<ListResult<UserListParams, UserSubsetMapping[T]>> {
57
- const { qb } = this.getSubsetQueries(subset);
58
-
59
- if (params.id) qb.whereIn("users.id", asArray(params.id));
60
- if (params.keyword) qb.where("users.email", "like", `%${params.keyword}%`);
61
- if (params.orderBy === "id-desc") qb.orderBy("users.id", "desc");
62
-
63
- return this.executeSubsetQuery({ subset, qb, params });
64
- }
65
- ```
66
-
67
- ### save
68
-
69
- ```typescript
70
- @api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"] })
71
- async save(spa: UserSaveParams[]): Promise<number[]> {
72
- const wdb = this.getPuri("w");
73
- spa.forEach((sp) => wdb.ubRegister("users", sp));
74
-
75
- return wdb.transaction(async (trx) => {
76
- return trx.ubUpsert("users");
77
- });
78
- }
79
- ```
80
-
81
- ### del
82
-
83
- ```typescript
84
- @api({ httpMethod: "POST", guards: ["admin"] })
85
- async del(ids: number[]): Promise<number> {
86
- const wdb = this.getPuri("w");
87
- await wdb.transaction(async (trx) => {
88
- return trx.table("users").whereIn("id", ids).delete();
89
- });
90
- return ids.length;
91
- }
92
- ```
93
-
94
- ## BaseModel Methods
95
-
96
- | Method | Description |
97
- | ----------------------------- | ------------------------------------------------- |
98
- | `getPuri("r")` | Read query builder |
99
- | `getPuri("w")` | Write query builder |
100
- | `getSubsetQueries(subset)` | Subset query builder (returns `{ qb, onSubset }`) |
101
- | `executeSubsetQuery(options)` | Execute subset query |
102
- | `createEnhancers(enhancers)` | Enhancer object creation helper (type inference) |
103
-
104
- ## getSubsetQueries
105
-
106
- ```typescript
107
- const { qb, onSubset } = this.getSubsetQueries(subset);
108
-
109
- // qb: query builder for adding conditions
110
- qb.where("users.status", "active");
111
-
112
- // onSubset: when you need the type for a specific subset
113
- const typedQb = onSubset("A"); // infers as subset A's type
114
- ```
115
-
116
- ## executeSubsetQuery Options
117
-
118
- ```typescript
119
- return this.executeSubsetQuery({
120
- subset, // subset key
121
- qb, // query builder
122
- params, // ListParams (num, page, queryMode, sonamuFilter, etc.)
123
- debug: true, // print query log (default: false)
124
- optimizeCountQuery: true, // COUNT query optimization - removes unnecessary LEFT JOINs (default: false)
125
- enhancers, // Enhancer function object (optional)
126
- });
127
- ```
128
-
129
- > **CRITICAL: Do not directly mutate the object returned by `executeSubsetQuery()`.**
130
- >
131
- > Replacing rows via `result.rows = result.rows.map(...)` or `(result as any).rows = ...`
132
- > will break the `total` count and cause pagination to malfunction.
133
- >
134
- > Use the `enhancers` pattern for virtual fields that require additional computation:
135
- >
136
- > ```typescript
137
- > // WRONG — breaks pagination
138
- > const result = await this.executeSubsetQuery({ subset, qb, params });
139
- > (result as any).rows = result.rows.map((row) => ({ ...row, extra: "value" }));
140
- > return result as any;
141
- >
142
- > // CORRECT — enhancers pattern
143
- > const enhancers = this.createEnhancers({
144
- > A: (row) => ({ ...row, extra: "value" }),
145
- > });
146
- > return this.executeSubsetQuery({ subset, qb, params, enhancers });
147
- > ```
148
-
149
- ### queryMode
150
-
151
- Pass queryMode in params to control the return value:
152
-
153
- ```typescript
154
- // List only (skip COUNT query) - performance optimization
155
- const { rows } = await this.findMany(subset, { ...params, queryMode: "list" });
156
-
157
- // Count only (skip list)
158
- const { total } = await this.findMany(subset, { ...params, queryMode: "count" });
159
-
160
- // Both (default)
161
- const { rows, total } = await this.findMany(subset, { ...params, queryMode: "both" });
162
- ```
163
-
164
- ### sonamuFilter (FilterQuery)
165
-
166
- Automatically apply filter conditions via params.sonamuFilter:
167
-
168
- **Prerequisite:** The corresponding prop in entity.json must have `"toFilter": true` set. Fields without this setting are excluded from filtering.
169
-
170
- ```typescript
171
- // Filter passed from the client
172
- const params = {
173
- num: 10,
174
- page: 1,
175
- sonamuFilter: {
176
- status: "active", // eq (default)
177
- age: { gte: 18 }, // >=
178
- role: { in: ["admin", "user"] },
179
- email: { contains: "@test" }, // LIKE %...%
180
- },
181
- };
182
-
183
- // Automatically applied in the Model
184
- return this.executeSubsetQuery({ subset, qb, params });
185
- ```
186
-
187
- **Allowed operators by type:**
188
-
189
- | Type | Operators |
190
- | ----------------- | -------------------------------------------------------------------- |
191
- | `string` | eq, ne, contains, startsWith, endsWith, in, notIn, isNull, isNotNull |
192
- | `integer` | eq, ne, gt, gte, lt, lte, in, notIn, between, isNull, isNotNull |
193
- | `numeric` | eq, ne, gt, gte, lt, lte, in, notIn, between, isNull, isNotNull |
194
- | `boolean` | eq, ne, isNull, isNotNull |
195
- | `date`/`datetime` | eq, ne, before, after, between, isNull, isNotNull |
196
- | `enum` | eq, ne, in, notIn, isNull, isNotNull |
197
- | `json` | isNull, isNotNull |
198
-
199
- **Operator examples:**
200
-
201
- | Operator | SQL | Example |
202
- | --------------------- | --------------- | ----------------------------------------- |
203
- | `eq` (default) | `=` | `{ status: "active" }` |
204
- | `ne` | `!=` | `{ status: { ne: "deleted" } }` |
205
- | `gt`, `gte` | `>`, `>=` | `{ age: { gte: 18 } }` |
206
- | `lt`, `lte` | `<`, `<=` | `{ price: { lte: 1000 } }` |
207
- | `in`, `notIn` | `IN`, `NOT IN` | `{ role: { in: ["a", "b"] } }` |
208
- | `contains` | `LIKE %...%` | `{ name: { contains: "kim" } }` |
209
- | `startsWith` | `LIKE ...%` | `{ code: { startsWith: "A" } }` |
210
- | `endsWith` | `LIKE %...` | `{ ext: { endsWith: ".pdf" } }` |
211
- | `isNull`, `isNotNull` | `IS NULL` | `{ deleted_at: { isNull: true } }` |
212
- | `before`, `after` | `<`, `>` (date) | `{ created_at: { after: "2024-01-01" } }` |
213
- | `between` | `BETWEEN` | `{ price: { between: [100, 500] } }` |
214
-
215
- **Type definition (`ApplySonamuFilter`):**
216
-
217
- ```typescript
218
- import type { ApplySonamuFilter } from "sonamu";
219
-
220
- // Define sonamuFilter type in ListParams
221
- type ProjectListParams = {
222
- num: number;
223
- page: number;
224
- sonamuFilter?: ApplySonamuFilter<
225
- ProjectSubsetA, // entity type
226
- "id" | "created_at", // fields to exclude (TOmitKeys)
227
- "budget" // fields to treat as numeric (TNumericKeys)
228
- >;
229
- };
230
- ```
231
-
232
- ## Enhancers
233
-
234
- Post-query processing for virtual field computation and similar needs:
235
-
236
- ```typescript
237
- // Define enhancer
238
- const enhancers = this.createEnhancers({
239
- A: async (row) => ({
240
- ...row,
241
- fullName: `${row.first_name} ${row.last_name}`,
242
- }),
243
- D: async (row) => ({
244
- ...row,
245
- age: calculateAge(row.birth_date),
246
- }),
247
- });
248
-
249
- // Use in executeSubsetQuery
250
- return this.executeSubsetQuery({ subset, qb, params, enhancers });
251
- ```
252
-
253
- ## Types File
254
-
255
- ```typescript
256
- // user.types.ts
257
- import { z } from "zod";
258
- import {
259
- UserOrderBy,
260
- UserSearchField,
261
- UserBaseSchema,
262
- UserBaseListParams,
263
- } from "../sonamu.generated";
264
-
265
- export const UserListParams = UserBaseListParams;
266
- export type UserListParams = z.infer<typeof UserListParams>;
267
-
268
- // Basic pattern: partial from BaseSchema
269
- export const UserSaveParams = UserBaseSchema.partial({
270
- id: true,
271
- created_at: true,
272
- });
273
- export type UserSaveParams = z.infer<typeof UserSaveParams>;
274
- ```
275
-
276
- ### SaveParams Patterns
277
-
278
- **Basic pattern (no relations):**
279
-
280
- ```typescript
281
- import { UserBaseSchema, UserBaseListParams } from "../sonamu.generated";
282
-
283
- export const UserListParams = UserBaseListParams;
284
- export type UserListParams = z.infer<typeof UserListParams>;
285
-
286
- export const UserSaveParams = UserBaseSchema.partial({
287
- id: true,
288
- created_at: true,
289
- });
290
- export type UserSaveParams = z.infer<typeof UserSaveParams>;
291
- ```
292
-
293
- **If a ManyToMany relation exists:**
294
-
295
- ```typescript
296
- // ManyToMany relation: add {relation_name}_ids array
297
- export const ProjectSaveParams = ProjectBaseSchema.partial({
298
- id: true,
299
- created_at: true,
300
- })
301
- .extend({
302
- employee_ids: z.array(z.number().int().positive()),
303
- tag_ids: z.array(z.number().int().positive()),
304
- })
305
- .omit({
306
- // omit virtual fields, system-generated fields, etc.
307
- virtual_test: true,
308
- });
309
- export type ProjectSaveParams = z.infer<typeof ProjectSaveParams>;
310
- ```
311
-
312
- **Handling nullable fields in BelongsToOne relations:**
313
-
314
- ```typescript
315
- // Nullable relations are automatically optional, so no extra partial is needed
316
- export const ResponseSaveParams = ResponseBaseSchema.partial({
317
- id: true,
318
- created_at: true,
319
- updated_at: true, // also make timestamp fields partial
320
- });
321
- export type ResponseSaveParams = z.infer<typeof ResponseSaveParams>;
322
- ```
323
-
324
- **Reference working code:**
325
-
326
- - `sonamu/examples/miomock/api/src/application/project/project.types.ts` - ManyToMany SaveParams example
327
- - `sonamu/examples/miomock/api/src/application/employee/employee.types.ts` - BelongsToOne SaveParams example
328
-
329
- ### Handling Relations in the Model
330
-
331
- **Removing relation objects on update:**
332
-
333
- ```typescript
334
- // Pattern used in tests for updates
335
- const original = await UserModel.findById("A", userId);
336
-
337
- // Remove relation object and extract FK only
338
- const { institution, ...userData } = original;
339
-
340
- await UserModel.save([
341
- {
342
- ...userData,
343
- institution_id: institution?.id ?? null, // explicitly add FK
344
- name: "Updated Name",
345
- },
346
- ]);
347
- ```
348
-
349
- **ManyToMany save:**
350
-
351
- ```typescript
352
- // ManyToMany is passed as an _ids array
353
- await ProjectModel.save([
354
- {
355
- id: projectId,
356
- title: "Updated",
357
- employee_ids: [1, 2, 3],
358
- tag_ids: [4, 5],
359
- },
360
- ]);
361
- ```
362
-
363
- **Reference working code:**
364
-
365
- - `sonamu/examples/miomock/api/src/application/project/project.model.ts` - ManyToMany save implementation
366
- - `sonamu/examples/miomock/api/src/application/project/project.model.test.ts` - Save test example