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.
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +5 -12
- package/dist/bin/cli.js +10 -253
- package/dist/database/base-model.d.ts +1 -0
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +14 -8
- package/dist/database/db.d.ts +2 -1
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +13 -3
- package/dist/database/puri-wrapper.d.ts.map +1 -1
- package/dist/database/puri-wrapper.js +4 -11
- package/dist/database/transaction-context.d.ts +8 -3
- package/dist/database/transaction-context.d.ts.map +1 -1
- package/dist/database/transaction-context.js +12 -7
- package/dist/migration/code-generation.js +2 -2
- package/dist/naite/naite-reporter.d.ts +8 -3
- package/dist/naite/naite-reporter.d.ts.map +1 -1
- package/dist/naite/naite-reporter.js +9 -4
- package/dist/ui-web/assets/{index-D0MHYbxl.js → index-CJf8uJYf.js} +47 -41
- package/dist/ui-web/assets/index-GMMIVGja.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/package.json +3 -3
- package/src/api/decorators.ts +11 -22
- package/src/bin/cli.ts +12 -375
- package/src/database/__tests__/transaction-scope.test.ts +227 -0
- package/src/database/base-model.ts +31 -6
- package/src/database/db.ts +17 -2
- package/src/database/puri-wrapper.ts +5 -22
- package/src/database/transaction-context.ts +14 -6
- package/src/migration/code-generation.ts +1 -1
- package/src/naite/naite-reporter.ts +8 -3
- package/dist/ui-web/assets/index-Dx_JX4aQ.css +0 -1
- package/src/skills/AGENTS.md +0 -108
- package/src/skills/sonamu/SKILL.md +0 -75
- package/src/skills/sonamu-ai-agents/SKILL.md +0 -205
- package/src/skills/sonamu-api/SKILL.md +0 -480
- package/src/skills/sonamu-auth/SKILL.md +0 -327
- package/src/skills/sonamu-auth/references/plugins.md +0 -310
- package/src/skills/sonamu-auth/references/user-id-migration-followups.md +0 -192
- package/src/skills/sonamu-auth/references/user-id-migration.md +0 -455
- package/src/skills/sonamu-config/SKILL.md +0 -203
- package/src/skills/sonamu-config/references/database.md +0 -452
- package/src/skills/sonamu-config/references/environments.md +0 -178
- package/src/skills/sonamu-config/references/server-options.md +0 -400
- package/src/skills/sonamu-entity/SKILL.md +0 -180
- package/src/skills/sonamu-entity/references/creation-workflow.md +0 -581
- package/src/skills/sonamu-entity/references/design-guides.md +0 -243
- package/src/skills/sonamu-entity/references/field-types.md +0 -170
- package/src/skills/sonamu-entity/references/relations-detail.md +0 -245
- package/src/skills/sonamu-entity/references/relations.md +0 -463
- package/src/skills/sonamu-entity/references/subset.md +0 -156
- package/src/skills/sonamu-fixture/SKILL.md +0 -180
- package/src/skills/sonamu-fixture/references/cli-usage.md +0 -439
- package/src/skills/sonamu-fixture/references/cone.md +0 -298
- package/src/skills/sonamu-frontend/SKILL.md +0 -142
- package/src/skills/sonamu-frontend/references/components.md +0 -323
- package/src/skills/sonamu-frontend/references/examples.md +0 -64
- package/src/skills/sonamu-frontend/references/hooks.md +0 -273
- package/src/skills/sonamu-frontend/references/runtime.md +0 -165
- package/src/skills/sonamu-frontend/references/scaffolding.md +0 -439
- package/src/skills/sonamu-i18n/SKILL.md +0 -287
- package/src/skills/sonamu-migration/SKILL.md +0 -316
- package/src/skills/sonamu-naite/SKILL.md +0 -266
- package/src/skills/sonamu-query/SKILL.md +0 -48
- package/src/skills/sonamu-query/references/model-patterns.md +0 -390
- package/src/skills/sonamu-query/references/model.md +0 -366
- package/src/skills/sonamu-query/references/puri.md +0 -413
- package/src/skills/sonamu-query/references/search.md +0 -238
- package/src/skills/sonamu-query/references/upsert.md +0 -324
- package/src/skills/sonamu-tasks/SKILL.md +0 -236
- package/src/skills/sonamu-testing/SKILL.md +0 -251
- package/src/skills/sonamu-testing/references/devrunner.md +0 -405
- package/src/skills/sonamu-testing/references/helpers.md +0 -185
- package/src/skills/sonamu-testing/references/patterns.md +0 -263
- package/src/skills/sonamu-testing/references/pitfalls.md +0 -588
- package/src/skills/sonamu-testing/references/quick-start.md +0 -285
- package/src/skills/sonamu-testing/references/type-safety.md +0 -172
- package/src/skills/sonamu-testing/references/writing-plan.md +0 -375
- 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
|