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,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