@famgia/omnify-laravel 0.0.119 → 0.0.121
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/{chunk-7I6UNXOD.js → chunk-NMX3TLZT.js} +8 -1
- package/dist/{chunk-7I6UNXOD.js.map → chunk-NMX3TLZT.js.map} +1 -1
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +7 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +4 -4
- package/stubs/ai-guides/claude-checklists/react.md.stub +108 -0
- package/stubs/ai-guides/cursor/omnify-schema.mdc.stub +339 -0
- package/stubs/ai-guides/cursor/react-design.mdc.stub +693 -0
- package/stubs/ai-guides/cursor/react-form.mdc.stub +277 -0
- package/stubs/ai-guides/cursor/react-services.mdc.stub +304 -0
- package/stubs/ai-guides/cursor/react.mdc.stub +336 -0
- package/stubs/ai-guides/cursor/schema-create.mdc.stub +344 -0
- package/stubs/ai-guides/react/README.md.stub +221 -0
- package/stubs/ai-guides/react/antd-guide.md.stub +457 -0
- package/stubs/ai-guides/react/checklist.md.stub +108 -0
- package/stubs/ai-guides/react/datetime-guide.md.stub +137 -0
- package/stubs/ai-guides/react/design-philosophy.md.stub +363 -0
- package/stubs/ai-guides/react/i18n-guide.md.stub +211 -0
- package/stubs/ai-guides/react/laravel-integration.md.stub +181 -0
- package/stubs/ai-guides/react/service-pattern.md.stub +180 -0
- package/stubs/ai-guides/react/tanstack-query.md.stub +339 -0
- package/stubs/ai-guides/react/types-guide.md.stub +671 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "React + Ant Design + TanStack Query rules for Omnify projects: component patterns, form validation with Zod, i18n, service layer, and generated types. Apply when creating React components, forms, or API integrations."
|
|
3
|
+
globs: ["{{TYPESCRIPT_BASE}}/**/*.{ts,tsx}"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# React + Ant Design Rules
|
|
8
|
+
|
|
9
|
+
> **Related Rules:**
|
|
10
|
+
> - **Form Development:** `.cursor/rules/omnify/react-form.mdc`
|
|
11
|
+
> - **Design System:** `.cursor/rules/omnify/react-design.mdc`
|
|
12
|
+
> - **Services:** `.cursor/rules/omnify/react-services.mdc`
|
|
13
|
+
> - **Schema Creation:** `.cursor/rules/omnify/schema-create.mdc` (mention `@schema-create` in chat)
|
|
14
|
+
|
|
15
|
+
## Guide Documentation
|
|
16
|
+
|
|
17
|
+
- Read `.claude/omnify/guides/react/` for patterns
|
|
18
|
+
|
|
19
|
+
## Critical Rules
|
|
20
|
+
|
|
21
|
+
1. **Use Ant Design** - Don't recreate existing components
|
|
22
|
+
2. **Use Omnify types** - Import from `@/types/model`, don't duplicate
|
|
23
|
+
3. **Use i18n** - Use `useTranslations()` for UI text
|
|
24
|
+
4. **Service Layer** - API calls in `services/`, not components
|
|
25
|
+
5. **TanStack Query** - For all server state, no `useState` + `useEffect`
|
|
26
|
+
|
|
27
|
+
## Quick Patterns
|
|
28
|
+
|
|
29
|
+
### Service
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
export const userService = {
|
|
33
|
+
list: (params?: UserListParams) => api.get("/api/users", { params }).then(r => r.data),
|
|
34
|
+
create: (input: UserCreate) => api.post("/api/users", input).then(r => r.data.data),
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Query
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const { data, isLoading } = useQuery({
|
|
42
|
+
queryKey: queryKeys.users.list(filters),
|
|
43
|
+
queryFn: () => userService.list(filters),
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Mutation
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const mutation = useMutation({
|
|
51
|
+
mutationFn: userService.create,
|
|
52
|
+
onSuccess: () => {
|
|
53
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
|
54
|
+
message.success(t("messages.created"));
|
|
55
|
+
},
|
|
56
|
+
onError: (error) => form.setFields(getFormErrors(error)),
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Types Rule
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// ✅ Use Omnify-generated types
|
|
64
|
+
import type { User, UserCreate } from "@/types/model";
|
|
65
|
+
|
|
66
|
+
// ✅ Only define query params locally
|
|
67
|
+
export interface UserListParams { ... }
|
|
68
|
+
|
|
69
|
+
// ❌ Don't redefine Omnify types
|
|
70
|
+
export interface UserCreateInput { ... } // WRONG
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## ⚠️ Enum Usage Rules (CRITICAL)
|
|
74
|
+
|
|
75
|
+
**ALWAYS use generated Enums** - NEVER inline union types or type extractions!
|
|
76
|
+
|
|
77
|
+
### ❌ BAD Patterns (FORBIDDEN)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// ❌ Complex type extraction - FORBIDDEN!
|
|
81
|
+
newFilter.approval_status = status as NonNullable<ListParams["filter"]>["approval_status"];
|
|
82
|
+
|
|
83
|
+
// ❌ Hardcoded union type - NOT DRY!
|
|
84
|
+
newFilter.approval_status = status as "pending" | "approved" | "rejected";
|
|
85
|
+
|
|
86
|
+
// ❌ Using string type - NO TYPE SAFETY!
|
|
87
|
+
const [status, setStatus] = useState<string>("");
|
|
88
|
+
|
|
89
|
+
// ❌ Hardcoded comparisons
|
|
90
|
+
if (status === "pending") { ... }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### ✅ GOOD Patterns (REQUIRED)
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// ✅ Import Enum from generated file
|
|
97
|
+
import {
|
|
98
|
+
ApprovalStatus,
|
|
99
|
+
ApprovalStatusValues,
|
|
100
|
+
isApprovalStatus,
|
|
101
|
+
getApprovalStatusLabel
|
|
102
|
+
} from "@/omnify/enum/ApprovalStatus";
|
|
103
|
+
|
|
104
|
+
// ✅ Type assertions with Enum
|
|
105
|
+
newFilter.approval_status = status as ApprovalStatus;
|
|
106
|
+
|
|
107
|
+
// ✅ State with Enum type
|
|
108
|
+
const [status, setStatus] = useState<ApprovalStatus | "">("");
|
|
109
|
+
// OR with default value:
|
|
110
|
+
const [status, setStatus] = useState<ApprovalStatus>(ApprovalStatus.Pending);
|
|
111
|
+
|
|
112
|
+
// ✅ Enum comparisons
|
|
113
|
+
if (status === ApprovalStatus.Pending) { ... }
|
|
114
|
+
|
|
115
|
+
// ✅ Iterate with Values array
|
|
116
|
+
const options = ApprovalStatusValues.map(value => ({
|
|
117
|
+
value,
|
|
118
|
+
label: getApprovalStatusLabel(value, locale)
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
// ✅ Type guard for validation
|
|
122
|
+
if (isApprovalStatus(unknownValue)) {
|
|
123
|
+
// unknownValue is ApprovalStatus here
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Generated Enum Files Structure
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
@/omnify/enum/
|
|
131
|
+
├── ApprovalStatus.ts
|
|
132
|
+
│ ├── ApprovalStatus (enum)
|
|
133
|
+
│ ├── ApprovalStatusValues (array of all values)
|
|
134
|
+
│ ├── isApprovalStatus() (type guard)
|
|
135
|
+
│ └── getApprovalStatusLabel() (i18n label)
|
|
136
|
+
├── OrderStatus.ts
|
|
137
|
+
└── ...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Select/Filter Options Pattern
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { ApprovalStatus, ApprovalStatusValues, getApprovalStatusLabel } from "@/omnify/enum/ApprovalStatus";
|
|
144
|
+
|
|
145
|
+
// ✅ Build options from Enum
|
|
146
|
+
const statusOptions = ApprovalStatusValues.map(value => ({
|
|
147
|
+
value,
|
|
148
|
+
label: getApprovalStatusLabel(value, locale)
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
<Select options={statusOptions} />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Form Pattern (Omnify)
|
|
155
|
+
|
|
156
|
+
> **Detailed Guide:** See `react-form.mdc` for complete form development guide.
|
|
157
|
+
|
|
158
|
+
**Quick Reference:**
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// 1. Imports
|
|
162
|
+
import { zodRule, setZodLocale } from '@/omnify/lib';
|
|
163
|
+
import { OmnifyForm } from '@/omnify/components';
|
|
164
|
+
import { customerSchemas, customerI18n, getCustomerFieldLabel } from '@/omnify/schemas';
|
|
165
|
+
|
|
166
|
+
// 2. Helper functions (MUST define in every form)
|
|
167
|
+
const LOCALE = 'ja';
|
|
168
|
+
const label = (key: string) => getCustomerFieldLabel(key, LOCALE);
|
|
169
|
+
const rule = (key: keyof typeof customerSchemas) => zodRule(customerSchemas[key], label(key));
|
|
170
|
+
|
|
171
|
+
// 3. Form.Item pattern
|
|
172
|
+
<Form.Item name="email" label={label('email')} rules={[rule('email')]}>
|
|
173
|
+
<Input />
|
|
174
|
+
</Form.Item>
|
|
175
|
+
|
|
176
|
+
// 4. Japanese compound fields
|
|
177
|
+
<OmnifyForm.JapaneseName schemas={customerSchemas} i18n={customerI18n} prefix="name" />
|
|
178
|
+
<OmnifyForm.JapaneseAddress form={form} schemas={customerSchemas} i18n={customerI18n} prefix="address" />
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## File Location
|
|
182
|
+
|
|
183
|
+
| What | Where |
|
|
184
|
+
| ----------------------- | ------------------------------------------- |
|
|
185
|
+
| Component (1 feature) | `features/{feature}/` |
|
|
186
|
+
| Component (2+ features) | `components/common/` |
|
|
187
|
+
| Service (API) | `services/` (ALWAYS) |
|
|
188
|
+
| Hook (1 feature) | `features/{feature}/` |
|
|
189
|
+
| Hook (2+ features) | `hooks/` |
|
|
190
|
+
| Zod Schema | `schemas/{model}.ts` |
|
|
191
|
+
| Validation utils | `lib/form-validation.ts`, `lib/zod-i18n.ts` |
|
|
192
|
+
|
|
193
|
+
## Ant Design v6 Deprecated Props
|
|
194
|
+
|
|
195
|
+
| Deprecated | Use Instead |
|
|
196
|
+
| -------------------------- | ----------------------- |
|
|
197
|
+
| `visible` | `open` |
|
|
198
|
+
| `direction` (Space) | `orientation` |
|
|
199
|
+
| `dropdownMatchSelectWidth` | `popupMatchSelectWidth` |
|
|
200
|
+
|
|
201
|
+
## Ant Design Static Method Warning
|
|
202
|
+
|
|
203
|
+
⚠️ **Warning:** `Static function can not consume context like dynamic theme`
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// ❌ Wrong - Static import has no context
|
|
207
|
+
import { message } from "antd";
|
|
208
|
+
message.success("Done"); // Warning!
|
|
209
|
+
|
|
210
|
+
// ✅ Correct - Use App.useApp() hook
|
|
211
|
+
import { App } from "antd";
|
|
212
|
+
|
|
213
|
+
function MyComponent() {
|
|
214
|
+
const { message, notification, modal } = App.useApp();
|
|
215
|
+
message.success("Done"); // ✅ No warning
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Note:**
|
|
220
|
+
- `App` component is already wrapped in `AntdThemeProvider`
|
|
221
|
+
- Just use `App.useApp()` in your component/hook
|
|
222
|
+
- Applies to: `message`, `notification`, `modal`
|
|
223
|
+
|
|
224
|
+
## Common Mistakes
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// ❌ Wrong
|
|
228
|
+
useEffect(() => { fetchData() }, []); // Use useQuery
|
|
229
|
+
const [users, setUsers] = useState([]); // Use TanStack for server state
|
|
230
|
+
<Button>Save</Button> // Use i18n
|
|
231
|
+
|
|
232
|
+
// ✅ Correct
|
|
233
|
+
const { data } = useQuery({ queryKey, queryFn });
|
|
234
|
+
<Button>{t("common.save")}</Button>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Form Validation Mistakes
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// ❌ Wrong - Hardcoded message in schema
|
|
241
|
+
z.string().min(1, "Name is required")
|
|
242
|
+
|
|
243
|
+
// ❌ Wrong - Define schema in component
|
|
244
|
+
const schema = z.object({ name: z.string() });
|
|
245
|
+
|
|
246
|
+
// ❌ Wrong - No field label comment
|
|
247
|
+
<Form.Item name="name" label={label("name")}>
|
|
248
|
+
|
|
249
|
+
// ✅ Correct - Schema in schemas/, messages in i18n/
|
|
250
|
+
import { userSchemas } from "@/schemas/user";
|
|
251
|
+
{/* Name */}
|
|
252
|
+
<Form.Item name="name" label={label("name")} rules={[zodRule(userSchemas.name, label("name"))]}>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Form.useForm() Warning
|
|
256
|
+
|
|
257
|
+
⚠️ **Warning:** `Instance created by useForm is not connected to any Form element`
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// ❌ Wrong - Export hook creates unused form instance
|
|
261
|
+
export function useUserForm() {
|
|
262
|
+
return Form.useForm(); // Instance not connected to any Form!
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ❌ Wrong - Create form instance but don't pass to Form
|
|
266
|
+
const [form] = Form.useForm();
|
|
267
|
+
return <Form>...</Form> // Missing form={form}
|
|
268
|
+
|
|
269
|
+
// ✅ Correct - Form instance must connect to Form
|
|
270
|
+
const [form] = Form.useForm();
|
|
271
|
+
return <Form form={form}>...</Form>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Rule:** Each `Form.useForm()` must have exactly one corresponding `<Form form={form}>`.
|
|
275
|
+
|
|
276
|
+
### Backend Validation Errors (422)
|
|
277
|
+
|
|
278
|
+
⚠️ **IMPORTANT:** Form must display errors from backend!
|
|
279
|
+
|
|
280
|
+
**Form component MUST receive `form` prop from parent:**
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// Form component
|
|
284
|
+
interface UserFormProps {
|
|
285
|
+
form: FormInstance; // ← REQUIRED
|
|
286
|
+
onSubmit: (values: UserCreate) => void;
|
|
287
|
+
// ...
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function UserForm({ form, onSubmit, ... }: UserFormProps) {
|
|
291
|
+
return (
|
|
292
|
+
<Form form={form} onFinish={onSubmit}>
|
|
293
|
+
...
|
|
294
|
+
</Form>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Page creates form and handles backend errors:**
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Page component
|
|
303
|
+
export default function NewUserPage() {
|
|
304
|
+
const [form] = Form.useForm(); // ← Create in parent
|
|
305
|
+
|
|
306
|
+
const mutation = useMutation({
|
|
307
|
+
mutationFn: userService.create,
|
|
308
|
+
onSuccess: () => { ... },
|
|
309
|
+
onError: (error) => {
|
|
310
|
+
form.setFields(getFormErrors(error)); // ← Set errors from backend
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<UserForm
|
|
316
|
+
form={form} // ← Pass to form component
|
|
317
|
+
onSubmit={(values) => mutation.mutate(values)}
|
|
318
|
+
/>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Helper `getFormErrors`** (available in `lib/api.ts`):
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { getFormErrors } from "@/lib/api";
|
|
327
|
+
|
|
328
|
+
// Converts Laravel 422 response to Ant Design format:
|
|
329
|
+
// { errors: { email: ["Email already exists"] } }
|
|
330
|
+
// → [{ name: "email", errors: ["Email already exists"] }]
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Rules:**
|
|
334
|
+
1. Form component does NOT create `Form.useForm()` - receives from parent
|
|
335
|
+
2. Page creates form instance and passes down
|
|
336
|
+
3. `onError` calls `form.setFields(getFormErrors(error))`
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Step-by-step schema creation workflow: 1) Read guide, 2) Create YAML, 3) npx omnify generate, 4) Validate output, 5) Migrate. CRITICAL: String defaults must be plain values without quotes wrapper!"
|
|
3
|
+
globs: ["schemas/**/*.yaml", "schemas/**/*.yml", "{{LARAVEL_BASE}}/database/migrations/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Omnify Schema Creation Workflow
|
|
8
|
+
|
|
9
|
+
> **Usage:** Mention `@schema-create` in chat OR this rule auto-applies when editing schema files
|
|
10
|
+
|
|
11
|
+
## 📋 Workflow Steps
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ 1. READ GUIDE → Read schema guide documentation │
|
|
16
|
+
│ 2. CREATE SCHEMA → Create/modify YAML schema file │
|
|
17
|
+
│ 3. GENERATE → Run `npx omnify generate` │
|
|
18
|
+
│ 4. VALIDATE → Check generated code matches requirements │
|
|
19
|
+
│ 5. MIGRATE → Run database migration │
|
|
20
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Step 1: Read Schema Guide 📖
|
|
26
|
+
|
|
27
|
+
**MUST READ before creating schema:**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Read the schema guide
|
|
31
|
+
cat .claude/omnify/guides/omnify/schema-guide.md
|
|
32
|
+
# OR
|
|
33
|
+
cat packages/omnify/ai-guides/schema-guide.md
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Key Documentation:**
|
|
37
|
+
- Schema syntax and property types
|
|
38
|
+
- Relationships (belongsTo, hasMany, etc.)
|
|
39
|
+
- Validation rules (nullable, minLength, maxLength)
|
|
40
|
+
- Display names (ja, en, vi)
|
|
41
|
+
- Special types (JapaneseName, JapaneseAddress, etc.)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Step 2: Create Schema 📝
|
|
46
|
+
|
|
47
|
+
### Schema File Location
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
schemas/
|
|
51
|
+
├── {domain}/ # Group by domain
|
|
52
|
+
│ ├── {Model}.yaml # PascalCase filename
|
|
53
|
+
│ └── {Enum}.yaml # Enum schemas
|
|
54
|
+
└── shared/ # Shared enums/types
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Basic Schema Template
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
# schemas/{domain}/{ModelName}.yaml
|
|
61
|
+
name: ModelName
|
|
62
|
+
kind: object
|
|
63
|
+
|
|
64
|
+
displayName:
|
|
65
|
+
ja: モデル名
|
|
66
|
+
en: Model Name
|
|
67
|
+
|
|
68
|
+
options:
|
|
69
|
+
timestamps: true # created_at, updated_at
|
|
70
|
+
softDelete: false # deleted_at
|
|
71
|
+
|
|
72
|
+
properties:
|
|
73
|
+
# Required field
|
|
74
|
+
name:
|
|
75
|
+
type: String
|
|
76
|
+
length: 100
|
|
77
|
+
displayName:
|
|
78
|
+
ja: 名前
|
|
79
|
+
en: Name
|
|
80
|
+
placeholder:
|
|
81
|
+
ja: 例:田中太郎
|
|
82
|
+
en: e.g. John Doe
|
|
83
|
+
|
|
84
|
+
# Optional field
|
|
85
|
+
description:
|
|
86
|
+
type: Text
|
|
87
|
+
nullable: true
|
|
88
|
+
displayName:
|
|
89
|
+
ja: 説明
|
|
90
|
+
en: Description
|
|
91
|
+
|
|
92
|
+
# Enum field
|
|
93
|
+
status:
|
|
94
|
+
type: OrderStatus
|
|
95
|
+
default: pending
|
|
96
|
+
displayName:
|
|
97
|
+
ja: ステータス
|
|
98
|
+
en: Status
|
|
99
|
+
|
|
100
|
+
# Relationship
|
|
101
|
+
category:
|
|
102
|
+
type: Category
|
|
103
|
+
relation: belongsTo
|
|
104
|
+
nullable: true
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### From SQL/Database Requirements
|
|
108
|
+
|
|
109
|
+
When creating schema from SQL or database design:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
# SQL: VARCHAR(255) NOT NULL
|
|
113
|
+
property_name:
|
|
114
|
+
type: String
|
|
115
|
+
length: 255
|
|
116
|
+
|
|
117
|
+
# SQL: VARCHAR(100) NULL
|
|
118
|
+
property_name:
|
|
119
|
+
type: String
|
|
120
|
+
length: 100
|
|
121
|
+
nullable: true
|
|
122
|
+
|
|
123
|
+
# SQL: TEXT
|
|
124
|
+
property_name:
|
|
125
|
+
type: Text
|
|
126
|
+
|
|
127
|
+
# SQL: INT / BIGINT
|
|
128
|
+
property_name:
|
|
129
|
+
type: Int # or BigInt
|
|
130
|
+
|
|
131
|
+
# SQL: DECIMAL(10,2)
|
|
132
|
+
property_name:
|
|
133
|
+
type: Float
|
|
134
|
+
|
|
135
|
+
# SQL: BOOLEAN DEFAULT false
|
|
136
|
+
property_name:
|
|
137
|
+
type: Boolean
|
|
138
|
+
default: false
|
|
139
|
+
|
|
140
|
+
# SQL: DATE
|
|
141
|
+
property_name:
|
|
142
|
+
type: Date
|
|
143
|
+
|
|
144
|
+
# SQL: DATETIME / TIMESTAMP
|
|
145
|
+
property_name:
|
|
146
|
+
type: DateTime
|
|
147
|
+
|
|
148
|
+
# SQL: ENUM('draft', 'published', 'archived')
|
|
149
|
+
# → Create separate enum schema
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Step 3: Generate Code 🔧
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Generate all code from schemas
|
|
158
|
+
npx omnify generate
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**What gets generated:**
|
|
162
|
+
- TypeScript types & Zod schemas
|
|
163
|
+
- Laravel migrations & models
|
|
164
|
+
- API resources & controllers
|
|
165
|
+
- Form requests & validation
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Step 4: Validate Generated Code ✅
|
|
170
|
+
|
|
171
|
+
### ⚠️ CRITICAL for SQL-based schemas
|
|
172
|
+
|
|
173
|
+
**If creating schema from SQL requirements, MUST verify:**
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# 1. Check generated migration matches SQL design
|
|
177
|
+
cat database/migrations/*_create_{table_name}_table.php
|
|
178
|
+
|
|
179
|
+
# 2. Check TypeScript types match requirements
|
|
180
|
+
cat resources/ts/omnify/schemas/{ModelName}.ts
|
|
181
|
+
|
|
182
|
+
# 3. Check Laravel model relationships
|
|
183
|
+
cat app/Models/{ModelName}.php
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Validation Checklist
|
|
187
|
+
|
|
188
|
+
| Check | What to Verify |
|
|
189
|
+
|-------|----------------|
|
|
190
|
+
| ✅ Column names | snake_case in migration matches schema |
|
|
191
|
+
| ✅ Column types | VARCHAR, TEXT, INT match schema types |
|
|
192
|
+
| ✅ Nullable | NULL/NOT NULL matches `nullable: true/false` |
|
|
193
|
+
| ✅ Defaults | DEFAULT values match `default:` in schema |
|
|
194
|
+
| ✅ Foreign keys | Relationships generate correct FK |
|
|
195
|
+
| ✅ Indexes | Required indexes are present |
|
|
196
|
+
|
|
197
|
+
### If Generated Code is Wrong
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
┌─────────────────────────────────────────┐
|
|
201
|
+
│ Generated code doesn't match SQL? │
|
|
202
|
+
│ │
|
|
203
|
+
│ 1. Fix the YAML schema │
|
|
204
|
+
│ 2. Run `npx omnify generate` again │
|
|
205
|
+
│ 3. Verify generated code │
|
|
206
|
+
│ 4. Repeat until correct │
|
|
207
|
+
└─────────────────────────────────────────┘
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Common fixes:**
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
# Problem: Column should be nullable
|
|
214
|
+
# Fix: Add nullable: true
|
|
215
|
+
property_name:
|
|
216
|
+
type: String
|
|
217
|
+
nullable: true # ← Add this
|
|
218
|
+
|
|
219
|
+
# Problem: Wrong column length
|
|
220
|
+
# Fix: Adjust length
|
|
221
|
+
property_name:
|
|
222
|
+
type: String
|
|
223
|
+
length: 255 # ← Change this
|
|
224
|
+
|
|
225
|
+
# Problem: Missing default value
|
|
226
|
+
# Fix: Add default
|
|
227
|
+
status:
|
|
228
|
+
type: OrderStatus
|
|
229
|
+
default: pending # ← Add this
|
|
230
|
+
|
|
231
|
+
# Problem: Wrong relationship type
|
|
232
|
+
# Fix: Change relation
|
|
233
|
+
category:
|
|
234
|
+
type: Category
|
|
235
|
+
relation: belongsTo # belongsTo, hasMany, hasOne, belongsToMany
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Step 5: Run Migration 🗄️
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Run migration
|
|
244
|
+
php artisan migrate
|
|
245
|
+
|
|
246
|
+
# If migration fails, rollback and fix
|
|
247
|
+
php artisan migrate:rollback
|
|
248
|
+
# Fix schema, regenerate, try again
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 🔄 Complete Workflow Example
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
# 1. Read guide
|
|
257
|
+
cat .claude/omnify/guides/omnify/schema-guide.md
|
|
258
|
+
|
|
259
|
+
# 2. Create schema file
|
|
260
|
+
# schemas/shop/Product.yaml
|
|
261
|
+
|
|
262
|
+
# 3. Generate code
|
|
263
|
+
npx omnify generate
|
|
264
|
+
|
|
265
|
+
# 4. Validate (especially for SQL-based requirements)
|
|
266
|
+
cat database/migrations/*_create_products_table.php
|
|
267
|
+
# If wrong: fix schema → regenerate → validate again
|
|
268
|
+
|
|
269
|
+
# 5. Run migration
|
|
270
|
+
php artisan migrate
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 📌 Quick Reference
|
|
276
|
+
|
|
277
|
+
### Property Types
|
|
278
|
+
|
|
279
|
+
| Type | SQL | TypeScript |
|
|
280
|
+
|------|-----|------------|
|
|
281
|
+
| `String` | VARCHAR | `string` |
|
|
282
|
+
| `Text` | TEXT | `string` |
|
|
283
|
+
| `LongText` | LONGTEXT | `string` |
|
|
284
|
+
| `Int` | INT | `number` |
|
|
285
|
+
| `BigInt` | BIGINT | `number` |
|
|
286
|
+
| `Float` | DECIMAL | `number` |
|
|
287
|
+
| `Boolean` | BOOLEAN | `boolean` |
|
|
288
|
+
| `Date` | DATE | `string` |
|
|
289
|
+
| `DateTime` | DATETIME | `string` |
|
|
290
|
+
| `Email` | VARCHAR | `string` |
|
|
291
|
+
| `Url` | VARCHAR | `string` |
|
|
292
|
+
| `Json` | JSON | `Record<string, unknown>` |
|
|
293
|
+
|
|
294
|
+
### Japanese Compound Types
|
|
295
|
+
|
|
296
|
+
| Type | Fields Generated |
|
|
297
|
+
|------|------------------|
|
|
298
|
+
| `JapaneseName` | `{prefix}_lastname`, `{prefix}_firstname`, `{prefix}_kana_lastname`, `{prefix}_kana_firstname` |
|
|
299
|
+
| `JapaneseAddress` | `{prefix}_postal_code`, `{prefix}_prefecture`, `{prefix}_address1`, `{prefix}_address2`, `{prefix}_address3` |
|
|
300
|
+
| `JapaneseBankAccount` | `{prefix}_bank_code`, `{prefix}_bank_name`, `{prefix}_branch_code`, etc. |
|
|
301
|
+
|
|
302
|
+
### Relationship Types
|
|
303
|
+
|
|
304
|
+
| Relation | Description | Foreign Key |
|
|
305
|
+
|----------|-------------|-------------|
|
|
306
|
+
| `belongsTo` | N:1 | `{property}_id` on this table |
|
|
307
|
+
| `hasMany` | 1:N | `{this_model}_id` on related table |
|
|
308
|
+
| `hasOne` | 1:1 | `{this_model}_id` on related table |
|
|
309
|
+
| `belongsToMany` | N:N | Pivot table |
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## ⚠️ Common Mistakes
|
|
314
|
+
|
|
315
|
+
```yaml
|
|
316
|
+
# ❌ CRITICAL: String defaults with quotes produce CURLY QUOTES!
|
|
317
|
+
default: "'cloud'" # ❌ Produces: ''cloud'' (broken!)
|
|
318
|
+
default: "'member'" # ❌ Produces: ''member'' (broken!)
|
|
319
|
+
|
|
320
|
+
# ✅ CORRECT: Just the value, no quotes wrapper
|
|
321
|
+
default: cloud # ✅ Produces: 'cloud'
|
|
322
|
+
default: member # ✅ Produces: 'member'
|
|
323
|
+
|
|
324
|
+
# ❌ Wrong: Missing displayName for non-English projects
|
|
325
|
+
name:
|
|
326
|
+
type: String
|
|
327
|
+
# Missing displayName!
|
|
328
|
+
|
|
329
|
+
# ✅ Correct: Always add displayName
|
|
330
|
+
name:
|
|
331
|
+
type: String
|
|
332
|
+
displayName:
|
|
333
|
+
ja: 名前
|
|
334
|
+
en: Name
|
|
335
|
+
|
|
336
|
+
# ❌ Wrong: Hardcoded enum values in property
|
|
337
|
+
status:
|
|
338
|
+
type: String
|
|
339
|
+
enum: [draft, published] # Don't do this
|
|
340
|
+
|
|
341
|
+
# ✅ Correct: Create separate enum schema
|
|
342
|
+
status:
|
|
343
|
+
type: PostStatus # Reference enum schema
|
|
344
|
+
```
|