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,285 +0,0 @@
1
- # Quick Start — Getting Started with Tests Quickly
2
-
3
-
4
- **Prerequisites**: scaffolding completed, nullable field handling in types.ts completed
5
-
6
- ### Step 1: Extend test-helpers.ts
7
-
8
- ```typescript
9
- // packages/api/src/application/__tests__/test-helpers.ts
10
-
11
- import { User, UserSaveParams } from "../user/user.types";
12
- import { Post, PostSaveParams } from "../post/post.types";
13
- import { Comment, CommentSaveParams } from "../comment/comment.types";
14
- import UserModel from "../user/user.model";
15
- import PostModel from "../post/post.model";
16
- import CommentModel from "../comment/comment.model";
17
-
18
- // User helper
19
- export async function createTestUser(params?: Partial<UserSaveParams>): Promise<number> {
20
- const user: UserSaveParams = {
21
- email: `test-${Date.now()}@example.com`,
22
- name: "Test User",
23
- ...params,
24
- };
25
- const [id] = await UserModel.save([user]);
26
- return id;
27
- }
28
-
29
- // User with dependencies (dependency chain)
30
- export async function createTestUserWithDeps() {
31
- const userId = await createTestUser();
32
- return { userId };
33
- }
34
-
35
- // Post helper
36
- export async function createTestPost(
37
- authorId: number,
38
- params?: Partial<PostSaveParams>,
39
- ): Promise<number> {
40
- const post: PostSaveParams = {
41
- author_id: authorId,
42
- title: "Test Post",
43
- content: "Test content",
44
- ...params,
45
- };
46
- const [id] = await PostModel.save([post]);
47
- return id;
48
- }
49
-
50
- // Post with dependencies
51
- export async function createTestPostWithDeps() {
52
- const { userId } = await createTestUserWithDeps();
53
- const postId = await createTestPost(userId);
54
- return { userId, postId };
55
- }
56
-
57
- // Comment helper
58
- export async function createTestComment(
59
- postId: number,
60
- authorId: number,
61
- params?: Partial<CommentSaveParams>,
62
- ): Promise<number> {
63
- const comment: CommentSaveParams = {
64
- post_id: postId,
65
- author_id: authorId,
66
- content: "Test comment",
67
- ...params,
68
- };
69
- const [id] = await CommentModel.save([comment]);
70
- return id;
71
- }
72
-
73
- // Comment with dependencies
74
- export async function createTestCommentWithDeps() {
75
- const { userId, postId } = await createTestPostWithDeps();
76
- const commentId = await createTestComment(postId, userId);
77
- return { userId, postId, commentId };
78
- }
79
- ```
80
-
81
- **CRITICAL patterns**:
82
-
83
- - `createTestX()`: basic creation helper (overridable via params)
84
- - `createTestXWithDeps()`: helper that automatically handles dependencies (creates all required data together)
85
- - FK fields use the `_id` suffix (`author_id`, `post_id`)
86
- - Returns: primarily returns ID; WithDeps returns an object with multiple IDs
87
-
88
- **CRITICAL: All required fields must be included!**
89
-
90
- Sonamu's `ubUpsert` uses PostgreSQL's `ON CONFLICT ... DO UPDATE` query.
91
- Even for updates, **all required fields (fields with NOT NULL constraints)** must be included.
92
-
93
- When required fields are missing:
94
-
95
- ```typescript
96
- // BAD - missing required field content
97
- const post: PostSaveParams = {
98
- author_id: authorId,
99
- title: "Test",
100
- // content missing! → ubUpsert ON CONFLICT UPDATE attempts to set NULL → DB error
101
- };
102
- // Error: null value in column "content" violates not-null constraint
103
- ```
104
-
105
- ### Distinguishing Required vs Optional Fields
106
-
107
- **1. Check entity.json**
108
-
109
- ```json
110
- // post.entity.json
111
- {
112
- "props": [
113
- { "name": "id", "type": "integer" }, // auto-generated - exclude
114
- { "name": "title", "type": "string", "length": 255 }, // required! (no nullable)
115
- { "name": "content", "type": "string" }, // required! (no nullable)
116
- { "name": "category", "type": "string", "nullable": true }, // optional (nullable)
117
- { "name": "author_id", "type": "integer" }, // required! (FK, no nullable)
118
- { "name": "view_count", "type": "integer", "dbDefault": "0" }, // required but has DB default
119
- { "name": "created_at", "type": "date", "dbDefault": "CURRENT_TIMESTAMP" } // automatic
120
- ]
121
- }
122
- ```
123
-
124
- **Required fields**: Fields **without** `nullable: true`
125
-
126
- - `title`, `content`, `author_id`
127
- - **Must** provide default values in test-helpers.ts
128
-
129
- **Optional fields**: Fields **with** `nullable: true`
130
-
131
- - `category`
132
- - Can be omitted in test-helpers.ts
133
-
134
- **Excluded fields**:
135
-
136
- - `id`: auto-increment (auto-generated on save)
137
- - `created_at`: automatically set by dbDefault
138
- - `view_count`: automatically set by dbDefault="0"
139
-
140
- **2. Write test-helpers.ts**
141
-
142
- ```typescript
143
- export async function createTestPost(
144
- authorId: number,
145
- params?: Partial<PostSaveParams>,
146
- ): Promise<number> {
147
- const post: PostSaveParams = {
148
- // Required fields must be included (fields without nullable)
149
- author_id: authorId,
150
- title: "Test Post", // required!
151
- content: "Test content", // required!
152
-
153
- // Optional fields can be omitted (fields with nullable: true)
154
- // category: null, // can be omitted
155
-
156
- // Fields with dbDefault can also be omitted
157
- // view_count: 0, // can be omitted since dbDefault="0"
158
-
159
- ...params, // allow override
160
- };
161
- const saved = await PostModel.save(post);
162
- return saved.id;
163
- }
164
- ```
165
-
166
- **Rule summary**:
167
-
168
- 1. Fields without `nullable: true` in entity.json = required fields
169
- 2. Required fields **must** have default values in test-helpers.ts
170
- 3. `id`, `created_at`, fields with `dbDefault` can be excluded
171
- 4. Required fields are also needed for ubUpsert's ON CONFLICT UPDATE
172
-
173
- ### Step 2: Write the test file
174
-
175
- ```typescript
176
- // packages/api/src/application/post/__tests__/post.test.ts
177
-
178
- import { bootstrap } from "sonamu";
179
- import { describe, test, expect, vi } from "vitest";
180
- import PostModel from "../post.model";
181
- import { createTestPostWithDeps } from "../../__tests__/test-helpers";
182
-
183
- bootstrap(vi); // CRITICAL: required!
184
-
185
- describe("PostModel", () => {
186
- describe("A. Create", () => {
187
- test("create post", async () => {
188
- const { userId, postId } = await createTestPostWithDeps();
189
-
190
- const post = await PostModel.findById(postId, ["A"]);
191
- expect(post.id).toBe(postId);
192
- expect(post.author_id).toBe(userId);
193
- });
194
- });
195
-
196
- describe("B. Read", () => {
197
- test("findById - Subset A", async () => {
198
- const { postId } = await createTestPostWithDeps();
199
-
200
- const post = await PostModel.findById(postId, ["A"]);
201
- expect(post.id).toBe(postId);
202
- expect(post).toHaveProperty("title");
203
- expect(post).toHaveProperty("content");
204
- });
205
-
206
- test("findMany - list query", async () => {
207
- await createTestPostWithDeps();
208
- await createTestPostWithDeps();
209
-
210
- const { rows } = await PostModel.findMany({ num: 10 });
211
- expect(rows.length).toBeGreaterThanOrEqual(2);
212
- });
213
- });
214
-
215
- describe("C. Update", () => {
216
- test("update post", async () => {
217
- const { postId } = await createTestPostWithDeps();
218
-
219
- await PostModel.save([
220
- {
221
- id: postId,
222
- title: "Updated Title",
223
- },
224
- ]);
225
-
226
- const updated = await PostModel.findById("A", postId);
227
- expect(updated.title).toBe("Updated Title");
228
- });
229
- });
230
-
231
- describe("D. Delete", () => {
232
- test("delete post", async () => {
233
- const { postId } = await createTestPostWithDeps();
234
-
235
- await PostModel.del(postId);
236
-
237
- const post = await PostModel.findById(postId, ["A"]);
238
- expect(post).toBeNull();
239
- });
240
- });
241
-
242
- describe("E. Business Logic", () => {
243
- test("full process from post creation to adding a comment", async () => {
244
- // 1. create post
245
- const { userId, postId } = await createTestPostWithDeps({
246
- title: "New Post",
247
- content: "Content",
248
- });
249
-
250
- // 2. another user writes a comment
251
- const commenterId = await createTestUser();
252
- const commentId = await createTestComment(postId, commenterId, {
253
- content: "Great post!",
254
- });
255
-
256
- // 3. fetch post (with comments)
257
- const post = await PostModel.findById(postId, ["A"]);
258
- expect(post.comments).toHaveLength(1);
259
- expect(post.comments[0].id).toBe(commentId);
260
- });
261
- });
262
- });
263
- ```
264
-
265
- **Pattern summary**:
266
-
267
- - `bootstrap(vi)` call is required
268
- - `describe` + `test` pattern (order: A. Create, B. Read, C. Update, D. Delete, E. Business Logic)
269
- - Use `createTestXWithDeps()` helper to automatically resolve dependencies
270
- - The Business Logic section is the most important! (implements real business scenarios)
271
-
272
- ### Step 3: Run tests
273
-
274
- ```bash
275
- # Start dev server if it's down
276
- pnpm sonamu dev
277
-
278
- # Tests during development (default)
279
- pnpm sonamu test
280
- pnpm sonamu test user.model
281
- ```
282
-
283
- **Done!** See the sections below for detailed information.
284
-
285
- ---
@@ -1,172 +0,0 @@
1
- # TypeScript Type Safety in Tests
2
-
3
- ## TypeScript Type Safety
4
-
5
- ### Optional Chaining Required When Indexing Arrays
6
-
7
- When accessing a property after indexing into an array, you must use optional chaining (`?.`).
8
-
9
- **Reason:**
10
-
11
- - Array indexing (`array[0]`, `array[1]`, etc.) can always return `undefined`
12
- - TypeScript infers the type of `array[0]` as `T | undefined`
13
- - Accessing a property without optional chaining causes a compile error
14
-
15
- **Wrong:**
16
-
17
- ```typescript
18
- // Type error: Object is possibly 'undefined'
19
- expect(list.rows[0].title).toBe("test");
20
- expect(searchResults.rows[0].name).toContain("keyword");
21
- ```
22
-
23
- **Correct:**
24
-
25
- ```typescript
26
- // Use optional chaining
27
- expect(list.rows[0]?.title).toBe("test");
28
- expect(searchResults.rows[0]?.name).toContain("keyword");
29
-
30
- // Or verify existence first, then access
31
- expect(list.rows.length).toBeGreaterThanOrEqual(1);
32
- expect(list.rows[0].title).toBe("test"); // now safe
33
- ```
34
-
35
- ### Recommended Patterns
36
-
37
- When accessing array elements in test code:
38
-
39
- **Pattern 1: Use optional chaining**
40
-
41
- ```typescript
42
- const result = await Model.findMany("A", { num: 10, page: 1 });
43
- expect(result.rows[0]?.field).toBe(expectedValue);
44
- ```
45
-
46
- **Pattern 2: Verify length, then access**
47
-
48
- ```typescript
49
- const result = await Model.findMany("A", { num: 10, page: 1 });
50
- expect(result.rows.length).toBeGreaterThanOrEqual(1);
51
- expect(result.rows[0].field).toBe(expectedValue); // type-safe
52
- ```
53
-
54
- **Pattern 3: Optional chaining required when using find()**
55
-
56
- ```typescript
57
- const list = await Model.findMany("A", { num: 10, page: 1 });
58
- const item = list.rows.find((r) => r.id === targetId);
59
- expect(item?.field).toBe(expectedValue); // find() can return undefined
60
- ```
61
-
62
- ### General Rules
63
-
64
- - Property access after array indexing: `array[0]?.property`
65
- - Results of `find()`, `filter()[0]`, etc.: always use `?.`
66
- - Nested object access: `obj.nested?.deep?.property`
67
- - Non-null assertion (`!`) only when certain
68
-
69
- ## Model Basic Methods (Test Targets)
70
-
71
- Sonamu Model provides the following methods by default. Tests are written targeting these methods:
72
-
73
- | Method | Purpose | Returns |
74
- | -------------------------- | ---------------------- | -------------------------------- |
75
- | `findById(subset, id)` | Fetch single record | `Promise<Subset>` |
76
- | `findMany(subset, params)` | Fetch list | `Promise<ListResult<Subset>>` |
77
- | `save(rows)` | Create/update (upsert) | `Promise<number[]>` (ids) |
78
- | `del(ids)` | Delete | `Promise<number>` (delete count) |
79
-
80
- **Note:** It's `del`, not `delete`. This avoids JavaScript reserved words.
81
-
82
-
83
- ## Type Safety Notes
84
-
85
- ### Zod Import Method
86
-
87
- **CRITICAL: Always use regular imports when importing Zod in test files.**
88
-
89
- ```typescript
90
- // CORRECT - in test files
91
- import { z } from "zod";
92
- import { describe, expect, vi } from "vitest";
93
-
94
- // WRONG - runtime error when using type import
95
- import type { z } from "zod"; // error when test runs!
96
- ```
97
-
98
- **Reason:** Because `z.infer<>` and Zod schemas are used directly in tests, the Zod object is needed at runtime.
99
-
100
- **Where this applies:**
101
-
102
- - `*.model.test.ts` - all test files
103
- - `test-helpers.ts` - helper files that use Zod schemas
104
-
105
- ### Checking partial Settings in SaveParams
106
-
107
- When testing `Model.save()`, you must check the `SaveParams` partial settings in `*.types.ts`:
108
-
109
- ```typescript
110
- // user.types.ts
111
- import { z } from "zod"; // regular import in types files too
112
- import { UserBaseSchema } from "../sonamu.generated";
113
-
114
- export const UserSaveParams = UserBaseSchema.partial({
115
- id: true, // auto-generated
116
- created_at: true, // auto-generated
117
- updated_at: true, // auto-generated
118
- });
119
- export type UserSaveParams = z.infer<typeof UserSaveParams>;
120
- ```
121
-
122
- ### Nullable Field Handling Pattern
123
-
124
- **→ See "Tasks to Do Immediately After Entity Creation" section above** (partial + extend + nullish pattern)
125
-
126
- ### Use Nullish Coalescing
127
-
128
- Nullish coalescing is required when a variable can be of type `T | undefined`:
129
-
130
- ```typescript
131
- // WRONG: userId may be number | undefined
132
- const user = await UserModel.findById("A", userId);
133
-
134
- // CORRECT: guard against undefined with nullish coalescing
135
- const user = await UserModel.findById("A", userId ?? 0);
136
- ```
137
-
138
- Especially be careful when using IDs created in a previous step:
139
-
140
- ```typescript
141
- const [userId] = await UserModel.save([{ ... }]);
142
-
143
- // WRONG: userId is number | undefined
144
- const user = await UserModel.findById("A", userId);
145
-
146
- // CORRECT:
147
- const user = await UserModel.findById("A", userId ?? 0);
148
- ```
149
-
150
- ### SaveParams Import Location
151
-
152
- SaveParams types are exported from each entity's types.ts, not from sonamu.generated.
153
-
154
- **Wrong:**
155
-
156
- ```typescript
157
- // test-helpers.ts
158
- import type { UserSaveParams, TaskSaveParams } from "../application/sonamu.generated"; // WRONG
159
- ```
160
-
161
- **Correct:**
162
-
163
- ```typescript
164
- // test-helpers.ts
165
- import type { UserSaveParams } from "../application/user/user.types";
166
- import type { TaskSaveParams } from "../application/task/task.types";
167
- ```
168
-
169
- **Reason:**
170
-
171
- - sonamu.generated only exports BaseSchema and BaseListParams
172
- - SaveParams is defined with BaseSchema.partial() in each entity's types.ts