@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.
@@ -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
+ ```