sonamu 0.10.5 → 0.10.7

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 (99) hide show
  1. package/dist/api/base-frame.d.ts +2 -0
  2. package/dist/api/base-frame.d.ts.map +1 -1
  3. package/dist/api/base-frame.js +8 -1
  4. package/dist/api/decorators.js +1 -1
  5. package/dist/api/sonamu.js +1 -1
  6. package/dist/bin/cli.js +195 -187
  7. package/dist/cone/cone-generator.js +3 -9
  8. package/dist/database/base-model.js +2 -2
  9. package/dist/database/knex.d.ts.map +1 -1
  10. package/dist/database/knex.js +2 -2
  11. package/dist/database/puri.d.ts +17 -0
  12. package/dist/database/puri.d.ts.map +1 -1
  13. package/dist/database/puri.js +102 -3
  14. package/dist/entity/entity-manager.js +1 -1
  15. package/dist/index.js +3 -3
  16. package/dist/migration/code-generation.js +6 -6
  17. package/dist/migration/migrator.d.ts.map +1 -1
  18. package/dist/migration/migrator.js +52 -7
  19. package/dist/migration/slack-confirm.js +1 -1
  20. package/dist/naite/naite-reporter.js +1 -1
  21. package/dist/syncer/checksum.js +1 -1
  22. package/dist/syncer/code-generator.js +1 -1
  23. package/dist/syncer/entity-operations.js +1 -1
  24. package/dist/syncer/syncer-actions.js +1 -1
  25. package/dist/syncer/syncer.js +1 -1
  26. package/dist/ui-web/assets/{index-BLX4zN8c.js → index-D0MHYbxl.js} +1 -1
  27. package/dist/ui-web/assets/index-Dx_JX4aQ.css +1 -0
  28. package/dist/ui-web/index.html +2 -2
  29. package/dist/utils/formatter.js +1 -1
  30. package/package.json +3 -3
  31. package/src/api/base-frame.ts +14 -1
  32. package/src/bin/cli.ts +268 -280
  33. package/src/cone/cone-generator.ts +2 -18
  34. package/src/database/__tests__/puri.test.ts +183 -0
  35. package/src/database/knex.ts +4 -1
  36. package/src/database/puri.ts +229 -2
  37. package/src/database/puri.types.test-d.ts +56 -0
  38. package/src/migration/code-generation.ts +5 -5
  39. package/src/migration/migrator.ts +81 -7
  40. package/src/skills/AGENTS.md +76 -50
  41. package/src/skills/sonamu/SKILL.md +46 -227
  42. package/src/skills/{sonamu/ai-agents.md → sonamu-ai-agents/SKILL.md} +3 -3
  43. package/src/skills/{sonamu/api.md → sonamu-api/SKILL.md} +3 -2
  44. package/src/skills/{sonamu/auth.md → sonamu-auth/SKILL.md} +9 -4
  45. package/src/skills/{sonamu/auth-plugins.md → sonamu-auth/references/plugins.md} +2 -7
  46. package/src/skills/sonamu-auth/references/user-id-migration-followups.md +192 -0
  47. package/src/skills/{sonamu/auth-migration.md → sonamu-auth/references/user-id-migration.md} +1 -195
  48. package/src/skills/sonamu-config/SKILL.md +203 -0
  49. package/src/skills/{sonamu → sonamu-config/references}/database.md +3 -8
  50. package/src/skills/sonamu-config/references/environments.md +178 -0
  51. package/src/skills/sonamu-config/references/server-options.md +400 -0
  52. package/src/skills/sonamu-entity/SKILL.md +180 -0
  53. package/src/skills/{sonamu/entity-validation-checklist.md → sonamu-entity/references/creation-workflow.md} +117 -8
  54. package/src/skills/sonamu-entity/references/design-guides.md +243 -0
  55. package/src/skills/sonamu-entity/references/field-types.md +170 -0
  56. package/src/skills/sonamu-entity/references/relations-detail.md +245 -0
  57. package/src/skills/{sonamu/entity-relations.md → sonamu-entity/references/relations.md} +1 -258
  58. package/src/skills/{sonamu → sonamu-entity/references}/subset.md +1 -11
  59. package/src/skills/sonamu-fixture/SKILL.md +180 -0
  60. package/src/skills/{sonamu/fixture-cli.md → sonamu-fixture/references/cli-usage.md} +5 -176
  61. package/src/skills/{sonamu → sonamu-fixture/references}/cone.md +4 -14
  62. package/src/skills/sonamu-frontend/SKILL.md +142 -0
  63. package/src/skills/sonamu-frontend/references/components.md +323 -0
  64. package/src/skills/sonamu-frontend/references/examples.md +64 -0
  65. package/src/skills/sonamu-frontend/references/hooks.md +273 -0
  66. package/src/skills/sonamu-frontend/references/runtime.md +165 -0
  67. package/src/skills/{sonamu → sonamu-frontend/references}/scaffolding.md +3 -8
  68. package/src/skills/{sonamu/i18n.md → sonamu-i18n/SKILL.md} +2 -2
  69. package/src/skills/{sonamu/migration.md → sonamu-migration/SKILL.md} +1 -1
  70. package/src/skills/{sonamu/naite.md → sonamu-naite/SKILL.md} +5 -5
  71. package/src/skills/sonamu-query/SKILL.md +48 -0
  72. package/src/skills/sonamu-query/references/model-patterns.md +390 -0
  73. package/src/skills/{sonamu → sonamu-query/references}/model.md +1 -401
  74. package/src/skills/{sonamu → sonamu-query/references}/puri.md +31 -243
  75. package/src/skills/sonamu-query/references/search.md +238 -0
  76. package/src/skills/{sonamu → sonamu-query/references}/upsert.md +1 -6
  77. package/src/skills/{sonamu/tasks.md → sonamu-tasks/SKILL.md} +1 -1
  78. package/src/skills/sonamu-testing/SKILL.md +251 -0
  79. package/src/skills/{sonamu/testing-devrunner.md → sonamu-testing/references/devrunner.md} +4 -9
  80. package/src/skills/sonamu-testing/references/helpers.md +185 -0
  81. package/src/skills/sonamu-testing/references/patterns.md +263 -0
  82. package/src/skills/sonamu-testing/references/pitfalls.md +588 -0
  83. package/src/skills/sonamu-testing/references/quick-start.md +285 -0
  84. package/src/skills/sonamu-testing/references/type-safety.md +172 -0
  85. package/src/skills/sonamu-testing/references/writing-plan.md +375 -0
  86. package/src/skills/{sonamu/vector.md → sonamu-vector/SKILL.md} +1 -1
  87. package/dist/ui-web/assets/index-Dx4ap5i4.css +0 -1
  88. package/src/skills/commands/sonamu-skills.md +0 -20
  89. package/src/skills/project/README.md +0 -19
  90. package/src/skills/project/architecture.md +0 -373
  91. package/src/skills/sonamu/cdd.md +0 -129
  92. package/src/skills/sonamu/config.md +0 -772
  93. package/src/skills/sonamu/create-sonamu.md +0 -208
  94. package/src/skills/sonamu/entity-basic.md +0 -678
  95. package/src/skills/sonamu/framework-change.md +0 -96
  96. package/src/skills/sonamu/frontend.md +0 -915
  97. package/src/skills/sonamu/project-init.md +0 -477
  98. package/src/skills/sonamu/skill-contribution.md +0 -247
  99. package/src/skills/sonamu/testing.md +0 -2163
@@ -0,0 +1,185 @@
1
+ # Test Helpers
2
+
3
+ ## Fixture
4
+
5
+ ### createFixtureLoader
6
+
7
+ ```typescript
8
+ // api/src/testing/fixture.ts
9
+ import { createFixtureLoader } from "sonamu/test";
10
+ import { CompanyModel } from "../application/company/company.model";
11
+ import { UserModel } from "../application/user/user.model";
12
+
13
+ export const loadFixtures = createFixtureLoader({
14
+ company01: async () => CompanyModel.findById("A", 1),
15
+ user01: async () => UserModel.findById("A", 1),
16
+ });
17
+ ```
18
+
19
+ ### Using in tests
20
+
21
+ ```typescript
22
+ import { loadFixtures } from "../../testing/fixture";
23
+
24
+ test("update company info", async () => {
25
+ const f0 = await loadFixtures(["company01"]);
26
+
27
+ await CompanyModel.save([
28
+ {
29
+ ...f0.company01,
30
+ name: "Updated Company",
31
+ },
32
+ ]);
33
+
34
+ const f1 = await loadFixtures(["company01"]);
35
+ expect(f1.company01.name).toBe("Updated Company");
36
+ });
37
+ ```
38
+
39
+ ## Naite (Test Tracing System)
40
+
41
+ **→ Detailed guide (key list, chaining filters, wildcard, del, internal structure): `sonamu-naite`**
42
+
43
+ Naite is a tracing system that records values with `Naite.t("key", value)` in source code and validates them with `Naite.get("key")` in tests.
44
+
45
+ ### Commonly Used Patterns in Tests
46
+
47
+ ```typescript
48
+ import { Naite } from "sonamu";
49
+
50
+ // Query validation
51
+ expect(Naite.get("esq-query").first()).not.contain("limit");
52
+
53
+ // UpsertBuilder behavior validation
54
+ const trace = Naite.get("puri:ub-upserted").first();
55
+ expect(trace).toMatchObject({ tableName: "users", rowCount: 3 });
56
+
57
+ // Fetch methods: .first(), .last(), .at(n), .result() (full array)
58
+ // Filters: .fromFile("user.model.ts"), .fromFunction("findById"), .where("data.tableName", "=", "users")
59
+ ```
60
+
61
+
62
+ ## Test Helper: expectQuery
63
+
64
+ Helper for validating specific parts of SQL queries (see miomock for reference):
65
+
66
+ ```typescript
67
+ // api/src/testing/expect-query.ts
68
+ import { type AST, Parser } from "node-sql-parser";
69
+ import { expect } from "vitest";
70
+
71
+ export type QueryPart =
72
+ | "type"
73
+ | "table"
74
+ | "columns"
75
+ | "set"
76
+ | "where"
77
+ | "join"
78
+ | "orderBy"
79
+ | "pagination"
80
+ | "groupBy"
81
+ | "having";
82
+
83
+ export function expectQuery(query: string, part?: QueryPart) {
84
+ if (!part) return expect(query);
85
+ const ast = parseQuery(query);
86
+ const extractedSql = extractors[part](ast);
87
+ return expect(extractedSql);
88
+ }
89
+ ```
90
+
91
+ ### Usage Examples
92
+
93
+ ```typescript
94
+ import { expectQuery } from "../testing/expect-query";
95
+
96
+ test("validate select query", async () => {
97
+ const db = UserModel.getPuri("r");
98
+ await db.table("users").select({ id: "users.id" });
99
+ const query = Naite.get("puri:executed-query").first();
100
+
101
+ expectQuery(query, "type").toBe("select");
102
+ expectQuery(query, "table").toBe("users");
103
+ expectQuery(query, "columns").toMatchInlineSnapshot(`""users"."id" AS \`id\`"`);
104
+ });
105
+
106
+ test("validate where condition", async () => {
107
+ const db = UserModel.getPuri("r");
108
+ await db.table("users").where("users.id", 1);
109
+ const query = Naite.get("puri:executed-query").first();
110
+
111
+ expectQuery(query, "where").toMatchInlineSnapshot(`""users"."id" = 1"`);
112
+ });
113
+
114
+ test("validate join", async () => {
115
+ const db = UserModel.getPuri("r");
116
+ await db.table("employees").leftJoin("departments", "employees.department_id", "departments.id");
117
+ const query = Naite.get("puri:executed-query").first();
118
+
119
+ expectQuery(query, "join").toMatchInlineSnapshot(
120
+ `"LEFT JOIN departments ON "employees"."department_id" = "departments"."id""`,
121
+ );
122
+ });
123
+ ```
124
+
125
+ ## Test Helper: expectUB
126
+
127
+ UpsertBuilder state validation helper (see miomock for reference):
128
+
129
+ ```typescript
130
+ // api/src/testing/expect-ub.ts
131
+ import type { UpsertBuilder } from "sonamu";
132
+ import { expect } from "vitest";
133
+
134
+ export type UBPart =
135
+ | "tables"
136
+ | "hasTable"
137
+ | "rowCount"
138
+ | "rows"
139
+ | "row"
140
+ | "refs"
141
+ | "uniquesMap"
142
+ | "uniqueIndexes";
143
+
144
+ export function expectUB<P extends UBPart>(
145
+ ub: UpsertBuilder,
146
+ part: P,
147
+ tableName?: string,
148
+ index?: number,
149
+ ) {
150
+ // ... implementation
151
+ }
152
+ ```
153
+
154
+ ### Usage Examples
155
+
156
+ ```typescript
157
+ import { expectUB } from "../testing/expect-ub";
158
+
159
+ test("validate UpsertBuilder state", async () => {
160
+ const ub = new UpsertBuilder();
161
+
162
+ // initial state
163
+ expectUB(ub, "hasTable", "users").toBe(false);
164
+ expectUB(ub, "tables").toEqual([]);
165
+
166
+ // after register
167
+ ub.register("users", {
168
+ email: "test@test.com",
169
+ username: "test",
170
+ password: "pw",
171
+ role: "normal",
172
+ });
173
+
174
+ expectUB(ub, "hasTable", "users").toBe(true);
175
+ expectUB(ub, "rowCount", "users").toBe(1);
176
+ expectUB(ub, "row", "users", 0).toMatchObject({
177
+ email: "test@test.com",
178
+ username: "test",
179
+ });
180
+
181
+ // confirm reset after upsert
182
+ await ub.upsert(wdb, "users");
183
+ expectUB(ub, "rowCount", "users").toBe(0);
184
+ });
185
+ ```
@@ -0,0 +1,263 @@
1
+ # Test Patterns
2
+
3
+ ## Enum Value Usage Rules
4
+
5
+ **CRITICAL: Only use enum values defined in entity.json.**
6
+
7
+ ### Rules
8
+
9
+ 1. Check the exact value list for enum fields in entity.json
10
+ 2. If possible, use TypeScript enum types from `sonamu.generated.ts` (type-safe)
11
+ 3. Set valid enum values as defaults in test-helpers.ts
12
+ 4. Do not use arbitrary strings
13
+
14
+ ```typescript
15
+ // WRONG: written based on guesses
16
+ role: "user"; // entity.json defines it as "normal"
17
+ status: "in_progress"; // entity.json defines it as "pending"
18
+
19
+ // CORRECT: written after checking entity.json
20
+ role: "normal"; // exact value from entity.json
21
+ status: "pending"; // exact value from entity.json
22
+
23
+ // BEST: use TypeScript enum
24
+ import { UserRoleEnum } from "../sonamu.generated";
25
+ role: UserRoleEnum.normal;
26
+ ```
27
+
28
+ **Core principle: entity.json is the Single Source of Truth.**
29
+
30
+
31
+ ## Test Basic Patterns
32
+
33
+ ### bootstrap
34
+
35
+ `bootstrap(vi)` call required in all test files:
36
+
37
+ ```typescript
38
+ import { bootstrap, test } from "sonamu/test";
39
+ import { describe, expect, vi } from "vitest";
40
+
41
+ bootstrap(vi);
42
+
43
+ describe("MyTest", () => {
44
+ test("test case", async () => {
45
+ // ...
46
+ });
47
+ });
48
+ ```
49
+
50
+ **bootstrap options:**
51
+
52
+ ```typescript
53
+ // Default: forTesting: true (fast, skips Syncer/Task)
54
+ bootstrap(vi);
55
+
56
+ // forTesting: false - full initialization (loads Syncer, Task, EntityManager, etc.)
57
+ // Used in tests for migrator, syncer, template, etc.
58
+ bootstrap(vi, { forTesting: false });
59
+ ```
60
+
61
+ ### test vs testAs
62
+
63
+ ```typescript
64
+ // Unauthenticated test - Context.user is null
65
+ test("unauthenticated test", async () => {
66
+ const me = await UserModel.me();
67
+ expect(me).toBeNull();
68
+ });
69
+
70
+ // Authenticated test - Context.user is set
71
+ import type { UserSubsetSS } from "../sonamu.generated";
72
+
73
+ const adminUser: UserSubsetSS = {
74
+ id: 1,
75
+ created_at: new Date(),
76
+ email: "admin@test.com",
77
+ username: "admin",
78
+ role: "admin",
79
+ };
80
+
81
+ testAs(adminUser, "admin permission test", async () => {
82
+ const me = await UserModel.me();
83
+ expect(me?.role).toBe("admin");
84
+ });
85
+ ```
86
+
87
+ ### test.each
88
+
89
+ ```typescript
90
+ test.each([
91
+ { input: "user@example.com", expected: true },
92
+ { input: "invalid-email", expected: false },
93
+ ])("email validation: $input → $expected", async ({ input, expected }) => {
94
+ expect(validateEmail(input)).toBe(expected);
95
+ });
96
+ ```
97
+
98
+
99
+ ## Mock Patterns
100
+
101
+ ### setup-mocks.ts
102
+
103
+ ```typescript
104
+ // api/src/testing/setup-mocks.ts
105
+ import { Naite } from "sonamu";
106
+ import { vi } from "vitest";
107
+
108
+ vi.mock("fs/promises", async (importOriginal) => {
109
+ const actual = (await importOriginal()) as typeof import("fs/promises");
110
+ return {
111
+ ...actual,
112
+ access: vi.fn((path, mode) => {
113
+ // virtual file system check
114
+ const vfs = Naite.get("mock:fs/promises:virtualFileSystem").result();
115
+ if (vfs.some((v) => v === path)) {
116
+ return Promise.resolve();
117
+ }
118
+ return actual.access(path, mode);
119
+ }),
120
+ writeFile: vi.fn((path, data) => {
121
+ Naite.t("fs/promises:writeFile", { path, data });
122
+ }),
123
+ rm: vi.fn(async (path, options) => {
124
+ Naite.t("fs/promises:rm", { path, options });
125
+ return Promise.resolve();
126
+ }),
127
+ };
128
+ });
129
+ ```
130
+
131
+ ### test-helpers.ts
132
+
133
+ ```typescript
134
+ // api/src/testing/test-helpers.ts
135
+ import { Entity, EntityManager, type EntityJson } from "sonamu";
136
+ import { vi } from "vitest";
137
+
138
+ // Mocking EntityManager.get
139
+ export function mockEntityManagerGet(
140
+ targetEntityId: string,
141
+ overrideCallback: (original: EntityJson) => EntityJson,
142
+ ) {
143
+ const originalEntityJson = EntityManager.get(targetEntityId).toJson();
144
+ const originalGet = EntityManager.get;
145
+ return vi.spyOn(EntityManager, "get").mockImplementation((entityId) => {
146
+ if (entityId === targetEntityId) {
147
+ return new Entity(overrideCallback(originalEntityJson));
148
+ }
149
+ return originalGet.call(EntityManager, entityId);
150
+ });
151
+ }
152
+ ```
153
+
154
+ ## CRUD Test Patterns
155
+
156
+ ### Create & Read
157
+
158
+ ```typescript
159
+ test("Create - create new user", async () => {
160
+ const [userId] = await UserModel.save([
161
+ {
162
+ email: "newuser@test.com",
163
+ username: "newuser",
164
+ password: "hashedpassword",
165
+ role: "normal",
166
+ },
167
+ ]);
168
+
169
+ expect(userId).toBeGreaterThan(0);
170
+
171
+ const user = await UserModel.findById("A", userId);
172
+ expect(user.email).toBe("newuser@test.com");
173
+ });
174
+ ```
175
+
176
+ ### Update
177
+
178
+ ```typescript
179
+ test("Update - update user", async () => {
180
+ const f0 = await loadFixtures(["user01"]);
181
+
182
+ await UserModel.save([
183
+ {
184
+ ...f0.user01,
185
+ username: "updated_username",
186
+ },
187
+ ]);
188
+
189
+ const f1 = await loadFixtures(["user01"]);
190
+ expect(f1.user01.username).toBe("updated_username");
191
+ });
192
+ ```
193
+
194
+ ### Error Tests
195
+
196
+ ```typescript
197
+ test("error when fetching non-existent user", async () => {
198
+ await expect(UserModel.findById("A", 99999)).rejects.toThrow("not found");
199
+ });
200
+
201
+ test("unresolved reference error", async () => {
202
+ const ub = new UpsertBuilder();
203
+ const companyRef = ub.register("companies", { name: "Test" });
204
+ ub.register("departments", { company_id: companyRef, name: "Dept" });
205
+
206
+ // attempt upsert in wrong order
207
+ await expect(ub.upsert(wdb, "departments")).rejects.toThrow(/unresolved reference/);
208
+ });
209
+ ```
210
+
211
+ ## Test Structuring Patterns
212
+
213
+ ```typescript
214
+ describe("UpsertBuilder", () => {
215
+ describe("A. Basic registration (register)", () => {
216
+ test("register() returns UBRef", async () => {
217
+ /* ... */
218
+ });
219
+ test("multiple register() calls accumulate rows", async () => {
220
+ /* ... */
221
+ });
222
+ });
223
+
224
+ describe("B. Table management", () => {
225
+ test("basic behavior of getTable()/hasTable()", async () => {
226
+ /* ... */
227
+ });
228
+ });
229
+
230
+ describe("C. Upsert execution", () => {
231
+ test("upsert() - insert new row", async () => {
232
+ /* ... */
233
+ });
234
+ test("upsert() - update existing row", async () => {
235
+ /* ... */
236
+ });
237
+ test("insertOnly() - insert only", async () => {
238
+ /* ... */
239
+ });
240
+ });
241
+
242
+ describe("D. Error handling", () => {
243
+ test("upsert on non-existent table → empty array", async () => {
244
+ /* ... */
245
+ });
246
+ test("unresolved reference → error", async () => {
247
+ /* ... */
248
+ });
249
+ });
250
+ });
251
+ ```
252
+
253
+ ## File Structure
254
+
255
+ ```
256
+ api/src/testing/
257
+ ├── fixture.ts # createFixtureLoader definition
258
+ ├── global.ts # globalSetup (dotenv, setup export)
259
+ ├── setup-mocks.ts # global Mock configuration
260
+ ├── test-helpers.ts # test utility functions
261
+ ├── expect-query.ts # SQL query validation helper
262
+ └── expect-ub.ts # UpsertBuilder validation helper
263
+ ```