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,165 +0,0 @@
|
|
|
1
|
-
# Provider, Utilities, Error Handling, SSR
|
|
2
|
-
|
|
3
|
-
## SonamuProvider
|
|
4
|
-
|
|
5
|
-
Global configuration used across react-components
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// App.tsx or root component
|
|
9
|
-
import { SonamuProvider } from "@sonamu-kit/react-components/contexts";
|
|
10
|
-
import type { SonamuFile } from "@sonamu-kit/react-components/contexts";
|
|
11
|
-
|
|
12
|
-
function App() {
|
|
13
|
-
// File uploader function (used by FileInput, useTypeForm)
|
|
14
|
-
const uploader = async (files: File[]): Promise<SonamuFile[]> => {
|
|
15
|
-
const formData = new FormData();
|
|
16
|
-
files.forEach(file => formData.append("files", file));
|
|
17
|
-
|
|
18
|
-
const response = await fetch("/api/upload", {
|
|
19
|
-
method: "POST",
|
|
20
|
-
body: formData,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
return response.json();
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Authentication state (optional)
|
|
27
|
-
const auth = {
|
|
28
|
-
user: currentUser,
|
|
29
|
-
loading: isLoading,
|
|
30
|
-
login: async (params) => { /* ... */ },
|
|
31
|
-
logout: async () => { /* ... */ },
|
|
32
|
-
refetch: async () => { /* ... */ },
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Internationalization function (optional)
|
|
36
|
-
const SD = (key: string) => dictionary[key] ?? key;
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<SonamuProvider uploader={uploader} auth={auth} SD={SD}>
|
|
40
|
-
{children}
|
|
41
|
-
</SonamuProvider>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Required Props:**
|
|
47
|
-
|
|
48
|
-
- `uploader`: `(files: File[]) => Promise<SonamuFile[]>` - Used by FileInput
|
|
49
|
-
- `auth`: Authentication state and functions (optional)
|
|
50
|
-
- `SD`: Internationalization function (optional)
|
|
51
|
-
|
|
52
|
-
## Utility Functions
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
import {
|
|
56
|
-
dateF,
|
|
57
|
-
datetimeF,
|
|
58
|
-
numF,
|
|
59
|
-
hidden,
|
|
60
|
-
arrayableToArray,
|
|
61
|
-
sqlDateToDateString,
|
|
62
|
-
} from "@sonamu-kit/react-components/lib";
|
|
63
|
-
|
|
64
|
-
// Date formatting
|
|
65
|
-
dateF(new Date()); // "2024-01-15"
|
|
66
|
-
dateF("2024-01-15T10:30:00"); // "2024-01-15"
|
|
67
|
-
datetimeF(new Date()); // "2024-01-15 10:30:00"
|
|
68
|
-
|
|
69
|
-
// Number formatting
|
|
70
|
-
numF(1234567); // "1,234,567"
|
|
71
|
-
|
|
72
|
-
// Conditional hidden class
|
|
73
|
-
<div className={hidden(isHidden)}>...</div>
|
|
74
|
-
|
|
75
|
-
// SQL date → date string
|
|
76
|
-
sqlDateToDateString("2024-01-15T10:30:00.000Z"); // "2024-01-15"
|
|
77
|
-
|
|
78
|
-
// Convert to array
|
|
79
|
-
arrayableToArray("single"); // ["single"]
|
|
80
|
-
arrayableToArray(["a", "b"]); // ["a", "b"]
|
|
81
|
-
arrayableToArray(undefined); // []
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Error Handling
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
import { isSonamuError } from "@/lib/sonamu.shared";
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
await UserService.save([
|
|
91
|
-
{
|
|
92
|
-
/* ... */
|
|
93
|
-
},
|
|
94
|
-
]);
|
|
95
|
-
} catch (error) {
|
|
96
|
-
if (isSonamuError(error)) {
|
|
97
|
-
console.log("Status:", error.code);
|
|
98
|
-
console.log("Message:", error.message);
|
|
99
|
-
error.issues.forEach((issue) => {
|
|
100
|
-
console.log(`${issue.path.join(".")}: ${issue.message}`);
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## SSR
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
// api/src/ssr/routes.ts
|
|
110
|
-
import { registerSSR } from "sonamu/ssr";
|
|
111
|
-
|
|
112
|
-
registerSSR({
|
|
113
|
-
path: "/companies/:companyId",
|
|
114
|
-
preload: (params) => [UserService.me(), CompanyService.findById("A", Number(params.companyId))],
|
|
115
|
-
});
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Project Name Change
|
|
119
|
-
|
|
120
|
-
After generating the project, you need to replace the "Sonamu" text in the frontend with your project name.
|
|
121
|
-
|
|
122
|
-
**4 files to update:**
|
|
123
|
-
|
|
124
|
-
1. **`packages/web/index.html`** - Browser tab title
|
|
125
|
-
|
|
126
|
-
```html
|
|
127
|
-
<title>{project-name}</title>
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
2. **`packages/web/src/routes/__root.tsx`** - TanStack Router head configuration (most important!)
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
head: () => ({
|
|
134
|
-
meta: [{ title: "{project-name}" }],
|
|
135
|
-
}),
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
**Important:** If you don't update `__root.tsx`, the title will revert to "Sonamu" on HMR!
|
|
139
|
-
|
|
140
|
-
3. **`packages/web/src/routes/index.tsx`** - Main page title
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
<h1 className="text-2xl font-bold mb-4">Welcome to {project - name}</h1>
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
4. **`packages/web/src/components/Sidebar.tsx`** - Sidebar app name
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
const title = isAdmin ? "Admin" : "{project-name}";
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**How to verify:**
|
|
153
|
-
|
|
154
|
-
- Check that the project name is shown in the browser tab
|
|
155
|
-
- Confirm that the tab title does not change on file save via HMR (if it does, `__root.tsx` is missing)
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Rules
|
|
160
|
-
|
|
161
|
-
- NEVER manually modify `services.generated.ts`
|
|
162
|
-
- MUST specify Subset parameter when calling APIs
|
|
163
|
-
- Use `Promise.all([...])` for parallel requests
|
|
164
|
-
|
|
165
|
-
---
|
|
@@ -1,439 +0,0 @@
|
|
|
1
|
-
# Scaffolding
|
|
2
|
-
|
|
3
|
-
## CRITICAL: Required Scaffolding Items
|
|
4
|
-
|
|
5
|
-
**The following 5 items must all be scaffolded for every entity:**
|
|
6
|
-
|
|
7
|
-
| Item | Description |
|
|
8
|
-
| ------------------- | ---------------------------------- |
|
|
9
|
-
| `model` | CRUD model based on BaseModelClass |
|
|
10
|
-
| `model_test` | Model test file |
|
|
11
|
-
| `view_list` | List view component |
|
|
12
|
-
| `view_search_input` | Search input component |
|
|
13
|
-
| `view_form` | Create/edit form component |
|
|
14
|
-
|
|
15
|
-
**DO NOT:** scaffold only model and model_test and skip the view-related items
|
|
16
|
-
|
|
17
|
-
## CLI Scaffold Commands
|
|
18
|
-
|
|
19
|
-
All scaffold types can be generated via CLI (in addition to Sonamu UI):
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pnpm sonamu scaffold model <entityId>
|
|
23
|
-
pnpm sonamu scaffold model_test <entityId>
|
|
24
|
-
pnpm sonamu scaffold view_list <entityId>
|
|
25
|
-
pnpm sonamu scaffold view_form <entityId>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
`model_test` generates the test file skeleton (`{entity}.model.test.ts`) with the correct imports and `bootstrap(vi)` setup. Run it from `packages/api` after the entity's types.ts and migration are ready.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Pre-Scaffolding Checklist
|
|
33
|
-
|
|
34
|
-
Run from the **`packages/api`** directory:
|
|
35
|
-
|
|
36
|
-
1. **Wait for entity change detection**: After creating an entity, wait for the syncer to auto-generate `types.ts` (2-3 seconds)
|
|
37
|
-
2. **Check types.ts file**: If not auto-generated, create it manually
|
|
38
|
-
3. **Create and run migration**: Create migration from Sonamu UI → `pnpm sonamu migrate run`
|
|
39
|
-
4. **Complete TypeScript build**: Run `pnpm build` to generate `.js` files in `dist/`
|
|
40
|
-
5. **Restart dev server**: `pnpm dev` (restart required after build)
|
|
41
|
-
|
|
42
|
-
## Post-Scaffolding Required Checklist
|
|
43
|
-
|
|
44
|
-
**CRITICAL: After scaffolding is complete, you must perform the following steps.**
|
|
45
|
-
|
|
46
|
-
### 1. Build Test
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
cd packages/api
|
|
50
|
-
pnpm build
|
|
51
|
-
|
|
52
|
-
cd packages/web
|
|
53
|
-
pnpm build
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
- [ ] API build succeeds
|
|
57
|
-
- [ ] Web build succeeds
|
|
58
|
-
|
|
59
|
-
### 2. Restart Dev Server
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
cd packages/api
|
|
63
|
-
pnpm dev
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
- [ ] Server running normally
|
|
67
|
-
|
|
68
|
-
### 3. If Relations Exist (Add i18n Keys)
|
|
69
|
-
|
|
70
|
-
**Required**: Must be done if the entity has a BelongsToOne or any relation
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
// packages/api/src/i18n/ko.ts
|
|
74
|
-
export default {
|
|
75
|
-
// ... existing keys
|
|
76
|
-
|
|
77
|
-
// Add for each entity with relations
|
|
78
|
-
"entity.Post.author_id": "Author",
|
|
79
|
-
"entity.Question.collection_id": "Collection",
|
|
80
|
-
"entity.Question.parent_id": "Parent Question",
|
|
81
|
-
"entity.Employee.department_id": "Department",
|
|
82
|
-
"entity.Task.principal_investigator_id": "Principal Investigator",
|
|
83
|
-
|
|
84
|
-
// ...
|
|
85
|
-
} as const;
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Pattern**: `entity.{EntityId}.{relation}_id`
|
|
89
|
-
|
|
90
|
-
- Add `_id` suffix to the relation name
|
|
91
|
-
- Example: `author` relation → `author_id` key
|
|
92
|
-
|
|
93
|
-
- [ ] i18n keys added for all entities with relations
|
|
94
|
-
|
|
95
|
-
### 4. Add OrderBy Cases (When values other than id-desc are used)
|
|
96
|
-
|
|
97
|
-
**Optional**: Perform if the OrderBy enum in entity.json has values other than `id-desc`
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// packages/api/src/application/{entity}/{entity}.model.ts
|
|
101
|
-
|
|
102
|
-
// Generated code
|
|
103
|
-
if (params.orderBy === "id-desc") {
|
|
104
|
-
qb.orderBy("posts.id", "desc");
|
|
105
|
-
} else {
|
|
106
|
-
exhaustive(params.orderBy); // type error!
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Fix: add the remaining cases
|
|
110
|
-
if (params.orderBy === "id-desc") {
|
|
111
|
-
qb.orderBy("posts.id", "desc");
|
|
112
|
-
} else if (params.orderBy === "created_at-desc") {
|
|
113
|
-
qb.orderBy("posts.created_at", "desc");
|
|
114
|
-
} else if (params.orderBy === "name-asc") {
|
|
115
|
-
qb.orderBy("posts.name", "asc");
|
|
116
|
-
} else {
|
|
117
|
-
exhaustive(params.orderBy); // no more type errors
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
- [ ] OrderBy cases added
|
|
122
|
-
|
|
123
|
-
### 5. Handle Nullable Fields in types.ts (Required Before Testing!)
|
|
124
|
-
|
|
125
|
-
**Required**: Handle nullable fields in types.ts for all entities
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
// packages/api/src/application/{entity}/{entity}.types.ts
|
|
129
|
-
|
|
130
|
-
// Generated code
|
|
131
|
-
export const PostSaveParams = PostBaseSchema.partial({
|
|
132
|
-
id: true,
|
|
133
|
-
created_at: true,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Fix: add nullable fields
|
|
137
|
-
export const PostSaveParams = PostBaseSchema.partial({
|
|
138
|
-
id: true,
|
|
139
|
-
created_at: true,
|
|
140
|
-
updated_at: true, // nullable field
|
|
141
|
-
category: true, // nullable field
|
|
142
|
-
description: true, // nullable field
|
|
143
|
-
}).extend({
|
|
144
|
-
updated_at: z.date().nullish(),
|
|
145
|
-
category: z.string().nullish(),
|
|
146
|
-
description: z.string().nullish(),
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
**Detailed guide**: See "Tasks to Do Immediately After Entity Creation" in `sonamu-testing`
|
|
151
|
-
|
|
152
|
-
- [ ] Nullable fields handled in types.ts for all entities
|
|
153
|
-
|
|
154
|
-
### Completion Confirmation
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
Post-scaffolding required checklist complete
|
|
158
|
-
→ Next step: write tests (`sonamu-testing`)
|
|
159
|
-
|
|
160
|
-
⚠️ If nullable fields in types.ts are not handled,
|
|
161
|
-
type errors will occur when writing tests!
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
## Common Errors
|
|
167
|
-
|
|
168
|
-
| Error | Cause | Fix |
|
|
169
|
-
| ------------------------------------------- | ----------------------------------------------------- | ------------------------------------------ |
|
|
170
|
-
| "Non-existent module path requested {Type}" | types.ts not created or not compiled | Wait/create manually → build → restart dev |
|
|
171
|
-
| exhaustive() type error | Only the first OrderBy value is handled automatically | See "4. Add OrderBy Cases" above |
|
|
172
|
-
| Missing i18n key (relation) | `author_id` vs `author` | See "3. If Relations Exist" above |
|
|
173
|
-
| IdAsyncSelect API mismatch | Old scaffolding template used | See "IdAsyncSelect API Migration" below |
|
|
174
|
-
|
|
175
|
-
## Detailed Explanations
|
|
176
|
-
|
|
177
|
-
### "Non-existent module path requested" Error
|
|
178
|
-
|
|
179
|
-
Scaffolding reads types exported from `dist/application/{entity}/{entity}.types.js` to register module paths.
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
// modules/sonamu/src/entity/entity.ts
|
|
183
|
-
const typesFilePath = path.join(
|
|
184
|
-
Sonamu.apiRootPath,
|
|
185
|
-
runtimePath(`dist/application/${typesModulePath}.js`),
|
|
186
|
-
);
|
|
187
|
-
if (await exists(typesFilePath)) {
|
|
188
|
-
// register type
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Automatic types.ts Generation Mechanism
|
|
193
|
-
|
|
194
|
-
When an entity is created, the syncer's `handleTruthSourceChanges` automatically runs the `init_types` template:
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
// modules/sonamu/src/syncer/syncer.ts - handleTruthSourceChanges function
|
|
198
|
-
if (entityId) {
|
|
199
|
-
const entity = EntityManager.get(entityId);
|
|
200
|
-
const typeFilePath = path.join(...);
|
|
201
|
-
if (entity.parentId === undefined && !(await exists(typeFilePath))) {
|
|
202
|
-
await generateTemplate("init_types", { entityId });
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
**Auto-generation conditions**:
|
|
208
|
-
|
|
209
|
-
- When `parentId` is absent (top-level entity)
|
|
210
|
-
- When the `types.ts` file does not yet exist
|
|
211
|
-
|
|
212
|
-
**Causes of errors**:
|
|
213
|
-
|
|
214
|
-
- Attempting scaffolding immediately after entity creation before the syncer has run
|
|
215
|
-
- types.ts was created but the build has not completed so the `.js` file is missing
|
|
216
|
-
|
|
217
|
-
**Resolution order** (run from `packages/api`):
|
|
218
|
-
|
|
219
|
-
1. Wait briefly after entity creation for the syncer to generate types.ts (2-3 seconds)
|
|
220
|
-
2. If types.ts is missing, create it manually (see template below)
|
|
221
|
-
3. Create migration (Sonamu UI) and run it (`pnpm sonamu migrate run`)
|
|
222
|
-
4. Compile TypeScript with `pnpm build`
|
|
223
|
-
5. Restart `pnpm dev`
|
|
224
|
-
6. Retry scaffolding
|
|
225
|
-
|
|
226
|
-
### Manual types.ts Creation (If Needed)
|
|
227
|
-
|
|
228
|
-
For cases where auto-generation did not occur due to a syncer timing issue:
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
// {entity}.types.ts
|
|
232
|
-
import type { z } from "zod";
|
|
233
|
-
import { {Entity}BaseListParams, {Entity}BaseSchema } from "../sonamu.generated";
|
|
234
|
-
|
|
235
|
-
// {Entity} - ListParams
|
|
236
|
-
export const {Entity}ListParams = {Entity}BaseListParams;
|
|
237
|
-
export type {Entity}ListParams = z.infer<typeof {Entity}ListParams>;
|
|
238
|
-
|
|
239
|
-
// {Entity} - SaveParams
|
|
240
|
-
export const {Entity}SaveParams = {Entity}BaseSchema.partial({ id: true, created_at: true });
|
|
241
|
-
export type {Entity}SaveParams = z.infer<typeof {Entity}SaveParams>;
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
**IMPORTANT: Entity with `updated_at` field**:
|
|
245
|
-
|
|
246
|
-
If the entity has an `updated_at` field defined, it must also be included in the partial of SaveParams.
|
|
247
|
-
Since the form does not accept `updated_at` as direct input, it must be optional to avoid type errors.
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
// For entities with updated_at
|
|
251
|
-
export const {Entity}SaveParams = {Entity}BaseSchema.partial({
|
|
252
|
-
id: true,
|
|
253
|
-
created_at: true,
|
|
254
|
-
updated_at: true // ← add this
|
|
255
|
-
});
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### exhaustive() Type Error
|
|
259
|
-
|
|
260
|
-
`exhaustive` is a utility function provided by sonamu.
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
import { exhaustive } from "sonamu";
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
The scaffolding template only handles the **first value** of the `OrderBy` enum automatically.
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
// Generated code
|
|
270
|
-
if (params.orderBy === "id-desc") {
|
|
271
|
-
qb.orderBy("posts.id", "desc");
|
|
272
|
-
} else {
|
|
273
|
-
exhaustive(params.orderBy); // remaining cases unhandled → type error
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
**Fix**: Manually add all OrderBy cases
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
if (params.orderBy === "id-desc") {
|
|
281
|
-
qb.orderBy("posts.id", "desc");
|
|
282
|
-
} else if (params.orderBy === "created_at-desc") {
|
|
283
|
-
qb.orderBy("posts.created_at", "desc");
|
|
284
|
-
} else {
|
|
285
|
-
exhaustive(params.orderBy);
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### i18n Key Error (relation prop)
|
|
290
|
-
|
|
291
|
-
An entity's relation prop is defined as `author`, and the i18n label in `sd.generated.ts` is generated as `entity.Post.author`.
|
|
292
|
-
**However**, the scaffolded form.tsx template uses the FK column name `author_id`.
|
|
293
|
-
|
|
294
|
-
```typescript
|
|
295
|
-
// Scaffolded form.tsx (actually generated code)
|
|
296
|
-
{SD("entity.Post.author_id")} // ← uses _id suffix
|
|
297
|
-
|
|
298
|
-
// sd.generated.ts (auto-generated keys)
|
|
299
|
-
"entity.Post.author": "Author" // ← no _id
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
**Fix (choose one)**:
|
|
303
|
-
|
|
304
|
-
1. **Manually add `_id` key to ko.ts** (recommended):
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
// packages/api/src/i18n/ko.ts
|
|
308
|
-
export default {
|
|
309
|
-
// ... existing keys
|
|
310
|
-
"entity.Post.author_id": "Author",
|
|
311
|
-
"entity.Question.collection_id": "Collection",
|
|
312
|
-
"entity.Question.parent_id": "Parent Question",
|
|
313
|
-
// ...
|
|
314
|
-
} as const;
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
2. **Remove `_id` from form.tsx** (requires manual edit):
|
|
318
|
-
|
|
319
|
-
```typescript
|
|
320
|
-
// Manual edit after scaffolding
|
|
321
|
-
{
|
|
322
|
-
SD("entity.Post.author");
|
|
323
|
-
} // remove _id
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
**Recommended**: First option - add the `_id` key to ko.ts. It is preserved during sync and can be reused across multiple forms.
|
|
327
|
-
|
|
328
|
-
### IdAsyncSelect API Migration
|
|
329
|
-
|
|
330
|
-
#### Background
|
|
331
|
-
|
|
332
|
-
When the `@sonamu-kit/react-components` package was updated, the IdAsyncSelect API changed, but the scaffolding generation code (`scaffolding/react-components.ts`) still generates code based on the old API.
|
|
333
|
-
|
|
334
|
-
Therefore, running `pnpm sonamu scaffold` generates wrapper components based on the old API, causing build errors in projects using the latest package.
|
|
335
|
-
|
|
336
|
-
#### Specific API Changes
|
|
337
|
-
|
|
338
|
-
**Old API (code generated by scaffolding):**
|
|
339
|
-
|
|
340
|
-
```typescript
|
|
341
|
-
export function UserIdAsyncSelect<T extends UserSubsetKey>({
|
|
342
|
-
subset,
|
|
343
|
-
value,
|
|
344
|
-
onValueChange,
|
|
345
|
-
listParams, // ← old API
|
|
346
|
-
textField = "name", // ← old API
|
|
347
|
-
pageField, // ← old API
|
|
348
|
-
...
|
|
349
|
-
}: UserIdAsyncSelectProps<T>) {
|
|
350
|
-
// manual state management
|
|
351
|
-
const [searchText, setSearchText] = useState("");
|
|
352
|
-
|
|
353
|
-
const handleSearch = useCallback((text: string) => {
|
|
354
|
-
setSearchText(text);
|
|
355
|
-
}, []);
|
|
356
|
-
|
|
357
|
-
return (
|
|
358
|
-
<AsyncSelect // ← old component
|
|
359
|
-
config={UserAsyncIdConfig}
|
|
360
|
-
subset={subset}
|
|
361
|
-
listParams={{ ...listParams, [textField]: searchText }}
|
|
362
|
-
textField={textField}
|
|
363
|
-
pageField={pageField}
|
|
364
|
-
onSearch={handleSearch}
|
|
365
|
-
...
|
|
366
|
-
/>
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
**New API (actual package API):**
|
|
372
|
-
|
|
373
|
-
```typescript
|
|
374
|
-
export function UserIdAsyncSelect<T extends UserSubsetKey>({
|
|
375
|
-
subset,
|
|
376
|
-
value,
|
|
377
|
-
onValueChange,
|
|
378
|
-
baseListParams, // ← new API
|
|
379
|
-
displayField = "name", // ← new API
|
|
380
|
-
// pageField removed // ← removed
|
|
381
|
-
...
|
|
382
|
-
}: UserIdAsyncSelectProps<T>) {
|
|
383
|
-
// no state management (handled internally)
|
|
384
|
-
|
|
385
|
-
return (
|
|
386
|
-
<IdAsyncSelect<number> // ← new component + generic
|
|
387
|
-
config={UserAsyncIdConfig}
|
|
388
|
-
subset={subset}
|
|
389
|
-
baseListParams={baseListParams}
|
|
390
|
-
displayField={displayField}
|
|
391
|
-
// search handled internally
|
|
392
|
-
...
|
|
393
|
-
/>
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
#### Key Changes
|
|
399
|
-
|
|
400
|
-
1. **Component name**: `AsyncSelect` → `IdAsyncSelect<T>` (generic added)
|
|
401
|
-
2. **Prop names**:
|
|
402
|
-
- `listParams` → `baseListParams`
|
|
403
|
-
- `textField` → `displayField`
|
|
404
|
-
- `pageField` removed
|
|
405
|
-
3. **Search logic**: external state management → internal handling (useState, useCallback, onSearch no longer needed)
|
|
406
|
-
4. **Generic type**: PK type must be specified explicitly (`<number>` or `<string>`)
|
|
407
|
-
|
|
408
|
-
#### Files That Need Updating
|
|
409
|
-
|
|
410
|
-
```
|
|
411
|
-
src/components/
|
|
412
|
-
├── user/UserIdAsyncSelect.tsx
|
|
413
|
-
├── account/AccountIdAsyncSelect.tsx
|
|
414
|
-
├── announcement/AnnouncementIdAsyncSelect.tsx
|
|
415
|
-
└── ... (all *IdAsyncSelect.tsx files)
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
#### Migration Checklist
|
|
419
|
-
|
|
420
|
-
- [ ] Change component import: `AsyncSelect` → `IdAsyncSelect`
|
|
421
|
-
- [ ] Add generic type parameter: `<number>` or `<string>` (depending on PK type)
|
|
422
|
-
- [ ] Update Props type definitions:
|
|
423
|
-
- [ ] `listParams` → `baseListParams`
|
|
424
|
-
- [ ] `textField` → `displayField`
|
|
425
|
-
- [ ] Remove `pageField`
|
|
426
|
-
- [ ] Remove manual state management:
|
|
427
|
-
- [ ] Remove `useState`, `useCallback`
|
|
428
|
-
- [ ] Remove `onSearch` handler
|
|
429
|
-
- [ ] Update prop names in JSX:
|
|
430
|
-
- [ ] `listParams={...}` → `baseListParams={...}`
|
|
431
|
-
- [ ] `textField={...}` → `displayField={...}`
|
|
432
|
-
- [ ] Remove `pageField`
|
|
433
|
-
- [ ] Remove `onSearch`
|
|
434
|
-
|
|
435
|
-
#### Why does this happen?
|
|
436
|
-
|
|
437
|
-
This occurs when the scaffolding generation code in Sonamu has not been updated to reflect the latest package API, while the user has modified the local Sonamu source and the package has been updated but the scaffolding template has not.
|
|
438
|
-
|
|
439
|
-
**Fix**: Manually update the generated components according to the checklist above, or update the scaffolding template in the Sonamu core to use the latest API.
|