@famgia/omnify-typescript 0.0.67 → 0.0.69

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 (43) hide show
  1. package/dist/{chunk-4L77AHAC.js → chunk-6I4O23X6.js} +521 -66
  2. package/dist/chunk-6I4O23X6.js.map +1 -0
  3. package/dist/index.cjs +761 -65
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +138 -2
  6. package/dist/index.d.ts +138 -2
  7. package/dist/index.js +227 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/plugin.cjs +624 -75
  10. package/dist/plugin.cjs.map +1 -1
  11. package/dist/plugin.d.cts +6 -0
  12. package/dist/plugin.d.ts +6 -0
  13. package/dist/plugin.js +96 -11
  14. package/dist/plugin.js.map +1 -1
  15. package/package.json +3 -3
  16. package/scripts/postinstall.js +29 -40
  17. package/stubs/JapaneseAddressField.tsx.stub +289 -0
  18. package/stubs/JapaneseBankField.tsx.stub +212 -0
  19. package/stubs/JapaneseNameField.tsx.stub +194 -0
  20. package/stubs/ai-guides/checklists/react.md.stub +108 -0
  21. package/stubs/ai-guides/cursor/react-design.mdc.stub +289 -0
  22. package/stubs/ai-guides/cursor/react-form.mdc.stub +277 -0
  23. package/stubs/ai-guides/cursor/react-services.mdc.stub +304 -0
  24. package/stubs/ai-guides/cursor/react.mdc.stub +254 -0
  25. package/stubs/ai-guides/react/README.md.stub +221 -0
  26. package/stubs/ai-guides/react/antd-guide.md.stub +294 -0
  27. package/stubs/ai-guides/react/checklist.md.stub +108 -0
  28. package/stubs/ai-guides/react/datetime-guide.md.stub +137 -0
  29. package/stubs/ai-guides/react/design-philosophy.md.stub +363 -0
  30. package/stubs/ai-guides/react/i18n-guide.md.stub +211 -0
  31. package/stubs/ai-guides/react/laravel-integration.md.stub +181 -0
  32. package/stubs/ai-guides/react/service-pattern.md.stub +180 -0
  33. package/stubs/ai-guides/react/tanstack-query.md.stub +339 -0
  34. package/stubs/ai-guides/react/types-guide.md.stub +524 -0
  35. package/stubs/components-index.ts.stub +13 -0
  36. package/stubs/form-validation.ts.stub +106 -0
  37. package/stubs/rules/index.ts.stub +48 -0
  38. package/stubs/rules/kana.ts.stub +291 -0
  39. package/stubs/use-form-mutation.ts.stub +117 -0
  40. package/stubs/zod-i18n.ts.stub +32 -0
  41. package/ai-guides/antdesign-guide.md +0 -401
  42. package/ai-guides/typescript-guide.md +0 -310
  43. package/dist/chunk-4L77AHAC.js.map +0 -1
@@ -0,0 +1,304 @@
1
+ ---
2
+ description: "Frontend Services layer - API communication with backend"
3
+ globs: ["{{TYPESCRIPT_BASE}}/services/**"]
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Frontend Services Rules
8
+
9
+ > **API Reference:** `storage/api-docs/api-docs.json` (OpenAPI 3.0)
10
+ > **Base Config:** `lib/api.ts`
11
+
12
+ ## ⛔ MUST: Check api-docs.json FIRST!
13
+
14
+ **BEFORE writing ANY service code, MUST read `storage/api-docs/api-docs.json`:**
15
+
16
+ ```bash
17
+ # 1. ALWAYS read api-docs.json first
18
+ cat storage/api-docs/api-docs.json | jq '.paths'
19
+
20
+ # 2. Check specific endpoint
21
+ cat storage/api-docs/api-docs.json | jq '.paths["/api/users"]'
22
+
23
+ # 3. Check request/response schemas
24
+ cat storage/api-docs/api-docs.json | jq '.components.schemas'
25
+ ```
26
+
27
+ ### Checklist (MANDATORY)
28
+
29
+ - [ ] **Read api-docs.json** - NO guessing!
30
+ - [ ] **Verify endpoint exists** - URL, method must match
31
+ - [ ] **Copy exact parameter names** - `filter[search]` not `search`
32
+ - [ ] **Match request body schema** - required fields, types
33
+ - [ ] **Match response schema** - data structure
34
+
35
+ ### ❌ DON'T:
36
+ ```typescript
37
+ // ❌ Guessing parameter names
38
+ search?: string; // WRONG - API uses filter[search]
39
+ sort_by?: string; // WRONG - API uses sort
40
+ sort_order?: "asc"|"desc"; // WRONG - API uses "-field" format
41
+ ```
42
+
43
+ ### ✅ DO:
44
+ ```typescript
45
+ // ✅ Copy exactly from api-docs.json
46
+
47
+ // Sort has enum? → Create union type!
48
+ export type UserSortField =
49
+ | "id" | "-id"
50
+ | "name_lastname" | "-name_lastname"
51
+ | "email" | "-email"
52
+ | "created_at" | "-created_at";
53
+
54
+ export interface UserListParams {
55
+ sort?: UserSortField; // Correct: type-safe from enum
56
+ filter?: {
57
+ search?: string; // Correct: maps to filter[search]
58
+ };
59
+ }
60
+ ```
61
+
62
+ ```bash
63
+ # Regenerate OpenAPI docs if needed
64
+ php artisan l5-swagger:generate
65
+ ```
66
+
67
+ ## Service File Structure
68
+
69
+ ```typescript
70
+ /**
71
+ * {Resource} Service
72
+ *
73
+ * CRUD operations for {Resource} resource.
74
+ * Maps to Laravel {Resource}Controller.
75
+ */
76
+
77
+ import api, { PaginatedResponse } from "@/lib/api";
78
+ import type { Resource, ResourceCreate, ResourceUpdate } from "@/types/model";
79
+
80
+ // =============================================================================
81
+ // Types - Only query params (Create/Update come from Omnify)
82
+ // =============================================================================
83
+
84
+ /** Sort fields - MUST match api-docs.json enum! */
85
+ export type ResourceSortField =
86
+ | "id" | "-id"
87
+ | "created_at" | "-created_at"
88
+ | "updated_at" | "-updated_at";
89
+ // Add other fields from api-docs.json enum
90
+
91
+ /** Query params for listing resources */
92
+ export interface ResourceListParams {
93
+ page?: number;
94
+ per_page?: number;
95
+ sort?: ResourceSortField; // Type-safe from enum
96
+ filter?: {
97
+ search?: string;
98
+ // Add other filters from api-docs.json
99
+ };
100
+ }
101
+
102
+ // =============================================================================
103
+ // Service
104
+ // =============================================================================
105
+
106
+ const BASE_URL = "/api/resources";
107
+
108
+ export const resourceService = {
109
+ /**
110
+ * Axios auto-serializes nested objects:
111
+ * { filter: { search: "x" } } → ?filter[search]=x
112
+ */
113
+ list: async (params?: ResourceListParams): Promise<PaginatedResponse<Resource>> => {
114
+ const { data } = await api.get(BASE_URL, { params });
115
+ return data;
116
+ },
117
+
118
+ get: async (id: number): Promise<Resource> => {
119
+ const { data } = await api.get(`${BASE_URL}/${id}`);
120
+ return data.data ?? data;
121
+ },
122
+
123
+ create: async (input: ResourceCreate): Promise<Resource> => {
124
+ const { data } = await api.post(BASE_URL, input);
125
+ return data.data ?? data;
126
+ },
127
+
128
+ update: async (id: number, input: ResourceUpdate): Promise<Resource> => {
129
+ const { data } = await api.put(`${BASE_URL}/${id}`, input);
130
+ return data.data ?? data;
131
+ },
132
+
133
+ delete: async (id: number): Promise<void> => {
134
+ await api.delete(`${BASE_URL}/${id}`);
135
+ },
136
+ };
137
+ ```
138
+
139
+ ## Types Rule
140
+
141
+ ```typescript
142
+ // ✅ Import model types from Omnify
143
+ import type { User, UserCreate, UserUpdate } from "@/types/model";
144
+
145
+ // ✅ Define only query/filter params locally
146
+ export interface UserListParams {
147
+ search?: string;
148
+ page?: number;
149
+ per_page?: number;
150
+ }
151
+
152
+ // ❌ DON'T redefine model types
153
+ export interface User { ... } // WRONG - use Omnify
154
+ export interface UserCreateInput { ... } // WRONG - use UserCreate
155
+ ```
156
+
157
+ ## Response Handling
158
+
159
+ ### Paginated List (GET /api/resources)
160
+ ```typescript
161
+ // OpenAPI: Returns { data: T[], links: {...}, meta: {...} }
162
+ list: async (params): Promise<PaginatedResponse<Resource>> => {
163
+ const { data } = await api.get(BASE_URL, { params });
164
+ return data; // Full response with data, links, meta
165
+ },
166
+ ```
167
+
168
+ ### Single Resource (GET /api/resources/:id)
169
+ ```typescript
170
+ // OpenAPI: Returns { data: T }
171
+ get: async (id: number): Promise<Resource> => {
172
+ const { data } = await api.get(`${BASE_URL}/${id}`);
173
+ return data.data ?? data; // Unwrap { data: T } wrapper
174
+ },
175
+ ```
176
+
177
+ ### Create/Update (POST/PUT)
178
+ ```typescript
179
+ // OpenAPI: Returns { data: T }
180
+ create: async (input: ResourceCreate): Promise<Resource> => {
181
+ const { data } = await api.post(BASE_URL, input);
182
+ return data.data ?? data; // Unwrap { data: T } wrapper
183
+ },
184
+ ```
185
+
186
+ ### Delete (DELETE /api/resources/:id)
187
+ ```typescript
188
+ // OpenAPI: Returns 204 No Content
189
+ delete: async (id: number): Promise<void> => {
190
+ await api.delete(`${BASE_URL}/${id}`);
191
+ },
192
+ ```
193
+
194
+ ## Auth Service Pattern (Sanctum)
195
+
196
+ For auth endpoints that need CSRF:
197
+
198
+ ```typescript
199
+ import api, { csrf } from "@/lib/api";
200
+
201
+ export const authService = {
202
+ login: async (input: LoginInput): Promise<void> => {
203
+ await csrf(); // Required for Sanctum
204
+ await api.post("/login", input);
205
+ },
206
+
207
+ logout: async (): Promise<void> => {
208
+ await api.post("/logout");
209
+ },
210
+
211
+ me: async (): Promise<User> => {
212
+ const { data } = await api.get<User>("/api/user");
213
+ return data;
214
+ },
215
+ };
216
+ ```
217
+
218
+ ## OpenAPI Parameter Mapping
219
+
220
+ Axios auto-serializes nested objects → **NO transform needed!**
221
+
222
+ ```typescript
223
+ // Frontend interface
224
+ { filter: { search: "John" }, sort: "-created_at", page: 1 }
225
+
226
+ // Axios auto-serializes to:
227
+ // ?filter[search]=John&sort=-created_at&page=1
228
+ ```
229
+
230
+ | OpenAPI Parameter | Frontend Interface | Notes |
231
+ | ----------------- | ------------------ | --------------------- |
232
+ | `filter[search]` | `filter.search` | Axios auto-serializes |
233
+ | `page` | `page` | Direct |
234
+ | `per_page` | `per_page` | Direct |
235
+ | `sort` | `sort` | Use enum type! |
236
+
237
+ ### Sort Field - MUST use enum!
238
+ ```typescript
239
+ // ❌ WRONG - not type-safe
240
+ sort?: string;
241
+
242
+ // ✅ CORRECT - copy enum from api-docs.json
243
+ export type ResourceSortField =
244
+ | "id" | "-id"
245
+ | "created_at" | "-created_at";
246
+
247
+ sort?: ResourceSortField; // Type-safe!
248
+ ```
249
+
250
+ ## Error Handling
251
+
252
+ Errors are handled globally in `lib/api.ts`. Services should:
253
+ - **NOT** catch errors (let them propagate to mutations/queries)
254
+ - **NOT** show messages (handled by TanStack Query callbacks)
255
+
256
+ ```typescript
257
+ // ❌ Wrong
258
+ create: async (input) => {
259
+ try {
260
+ const { data } = await api.post(BASE_URL, input);
261
+ return data.data;
262
+ } catch (e) {
263
+ console.error(e); // DON'T catch here
264
+ }
265
+ },
266
+
267
+ // ✅ Correct - let errors propagate
268
+ create: async (input) => {
269
+ const { data } = await api.post(BASE_URL, input);
270
+ return data.data ?? data;
271
+ },
272
+ ```
273
+
274
+ ## Naming Conventions
275
+
276
+ | Item | Convention | Example |
277
+ | -------------- | --------------------------- | ---------------------------- |
278
+ | File | `{resource}.ts` (singular) | `users.ts`, `auth.ts` |
279
+ | Service object | `{resource}Service` | `userService`, `authService` |
280
+ | List params | `{Resource}ListParams` | `UserListParams` |
281
+ | Base URL | `/api/{resources}` (plural) | `/api/users` |
282
+
283
+ ## Checklist Before Creating Service
284
+
285
+ - [ ] API endpoint exists in `storage/api-docs/api-docs.json`
286
+ - [ ] Omnify types exist in `types/model/`
287
+ - [ ] Request body matches OpenAPI `requestBody.content.application/json.schema`
288
+ - [ ] Response matches OpenAPI `responses.200.content.application/json.schema`
289
+ - [ ] Parameters match OpenAPI `parameters[]`
290
+
291
+ ## Quick Reference: Current API Endpoints
292
+
293
+ > From `storage/api-docs/api-docs.json`:
294
+
295
+ ### Users API
296
+ | Method | Endpoint | Description |
297
+ | ------ | ----------------- | -------------------------------- |
298
+ | GET | `/api/users` | Paginated list with search, sort |
299
+ | POST | `/api/users` | Create user |
300
+ | GET | `/api/users/{id}` | Get single user |
301
+ | PUT | `/api/users/{id}` | Update user |
302
+ | DELETE | `/api/users/{id}` | Delete user |
303
+
304
+ **Sort fields:** `id`, `name_lastname`, `name_firstname`, `email`, `created_at`, `updated_at`
@@ -0,0 +1,254 @@
1
+ ---
2
+ description: "React + Ant Design frontend development rules"
3
+ globs: ["{{TYPESCRIPT_BASE}}/**"]
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
+
14
+ ## Guide Documentation
15
+
16
+ - Read `.claude/guides/react/` for patterns
17
+
18
+ ## Critical Rules
19
+
20
+ 1. **Use Ant Design** - Don't recreate existing components
21
+ 2. **Use Omnify types** - Import from `@/types/model`, don't duplicate
22
+ 3. **Use i18n** - Use `useTranslations()` for UI text
23
+ 4. **Service Layer** - API calls in `services/`, not components
24
+ 5. **TanStack Query** - For all server state, no `useState` + `useEffect`
25
+
26
+ ## Quick Patterns
27
+
28
+ ### Service
29
+
30
+ ```typescript
31
+ export const userService = {
32
+ list: (params?: UserListParams) => api.get("/api/users", { params }).then(r => r.data),
33
+ create: (input: UserCreate) => api.post("/api/users", input).then(r => r.data.data),
34
+ };
35
+ ```
36
+
37
+ ### Query
38
+
39
+ ```typescript
40
+ const { data, isLoading } = useQuery({
41
+ queryKey: queryKeys.users.list(filters),
42
+ queryFn: () => userService.list(filters),
43
+ });
44
+ ```
45
+
46
+ ### Mutation
47
+
48
+ ```typescript
49
+ const mutation = useMutation({
50
+ mutationFn: userService.create,
51
+ onSuccess: () => {
52
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
53
+ message.success(t("messages.created"));
54
+ },
55
+ onError: (error) => form.setFields(getFormErrors(error)),
56
+ });
57
+ ```
58
+
59
+ ## Types Rule
60
+
61
+ ```typescript
62
+ // ✅ Use Omnify-generated types
63
+ import type { User, UserCreate } from "@/types/model";
64
+
65
+ // ✅ Only define query params locally
66
+ export interface UserListParams { ... }
67
+
68
+ // ❌ Don't redefine Omnify types
69
+ export interface UserCreateInput { ... } // WRONG
70
+ ```
71
+
72
+ ## Form Pattern (Omnify)
73
+
74
+ > **Detailed Guide:** See `react-form.mdc` for complete form development guide.
75
+
76
+ **Quick Reference:**
77
+
78
+ ```typescript
79
+ // 1. Imports
80
+ import { zodRule, setZodLocale } from '@/omnify/lib';
81
+ import { OmnifyForm } from '@/omnify/components';
82
+ import { customerSchemas, customerI18n, getCustomerFieldLabel } from '@/omnify/schemas';
83
+
84
+ // 2. Helper functions (MUST define in every form)
85
+ const LOCALE = 'ja';
86
+ const label = (key: string) => getCustomerFieldLabel(key, LOCALE);
87
+ const rule = (key: keyof typeof customerSchemas) => zodRule(customerSchemas[key], label(key));
88
+
89
+ // 3. Form.Item pattern
90
+ <Form.Item name="email" label={label('email')} rules={[rule('email')]}>
91
+ <Input />
92
+ </Form.Item>
93
+
94
+ // 4. Japanese compound fields
95
+ <OmnifyForm.JapaneseName schemas={customerSchemas} i18n={customerI18n} prefix="name" />
96
+ <OmnifyForm.JapaneseAddress form={form} schemas={customerSchemas} i18n={customerI18n} prefix="address" />
97
+ ```
98
+
99
+ ## File Location
100
+
101
+ | What | Where |
102
+ | ----------------------- | ------------------------------------------- |
103
+ | Component (1 feature) | `features/{feature}/` |
104
+ | Component (2+ features) | `components/common/` |
105
+ | Service (API) | `services/` (ALWAYS) |
106
+ | Hook (1 feature) | `features/{feature}/` |
107
+ | Hook (2+ features) | `hooks/` |
108
+ | Zod Schema | `schemas/{model}.ts` |
109
+ | Validation utils | `lib/form-validation.ts`, `lib/zod-i18n.ts` |
110
+
111
+ ## Ant Design v6 Deprecated Props
112
+
113
+ | Deprecated | Use Instead |
114
+ | -------------------------- | ----------------------- |
115
+ | `visible` | `open` |
116
+ | `direction` (Space) | `orientation` |
117
+ | `dropdownMatchSelectWidth` | `popupMatchSelectWidth` |
118
+
119
+ ## Ant Design Static Method Warning
120
+
121
+ ⚠️ **Warning:** `Static function can not consume context like dynamic theme`
122
+
123
+ ```typescript
124
+ // ❌ Wrong - Static import has no context
125
+ import { message } from "antd";
126
+ message.success("Done"); // Warning!
127
+
128
+ // ✅ Correct - Use App.useApp() hook
129
+ import { App } from "antd";
130
+
131
+ function MyComponent() {
132
+ const { message, notification, modal } = App.useApp();
133
+ message.success("Done"); // ✅ No warning
134
+ }
135
+ ```
136
+
137
+ **Note:**
138
+ - `App` component is already wrapped in `AntdThemeProvider`
139
+ - Just use `App.useApp()` in your component/hook
140
+ - Applies to: `message`, `notification`, `modal`
141
+
142
+ ## Common Mistakes
143
+
144
+ ```typescript
145
+ // ❌ Wrong
146
+ useEffect(() => { fetchData() }, []); // Use useQuery
147
+ const [users, setUsers] = useState([]); // Use TanStack for server state
148
+ <Button>Save</Button> // Use i18n
149
+
150
+ // ✅ Correct
151
+ const { data } = useQuery({ queryKey, queryFn });
152
+ <Button>{t("common.save")}</Button>
153
+ ```
154
+
155
+ ### Form Validation Mistakes
156
+
157
+ ```typescript
158
+ // ❌ Wrong - Hardcoded message in schema
159
+ z.string().min(1, "Name is required")
160
+
161
+ // ❌ Wrong - Define schema in component
162
+ const schema = z.object({ name: z.string() });
163
+
164
+ // ❌ Wrong - No field label comment
165
+ <Form.Item name="name" label={label("name")}>
166
+
167
+ // ✅ Correct - Schema in schemas/, messages in i18n/
168
+ import { userSchemas } from "@/schemas/user";
169
+ {/* Name */}
170
+ <Form.Item name="name" label={label("name")} rules={[zodRule(userSchemas.name, label("name"))]}>
171
+ ```
172
+
173
+ ### Form.useForm() Warning
174
+
175
+ ⚠️ **Warning:** `Instance created by useForm is not connected to any Form element`
176
+
177
+ ```typescript
178
+ // ❌ Wrong - Export hook creates unused form instance
179
+ export function useUserForm() {
180
+ return Form.useForm(); // Instance not connected to any Form!
181
+ }
182
+
183
+ // ❌ Wrong - Create form instance but don't pass to Form
184
+ const [form] = Form.useForm();
185
+ return <Form>...</Form> // Missing form={form}
186
+
187
+ // ✅ Correct - Form instance must connect to Form
188
+ const [form] = Form.useForm();
189
+ return <Form form={form}>...</Form>
190
+ ```
191
+
192
+ **Rule:** Each `Form.useForm()` must have exactly one corresponding `<Form form={form}>`.
193
+
194
+ ### Backend Validation Errors (422)
195
+
196
+ ⚠️ **IMPORTANT:** Form must display errors from backend!
197
+
198
+ **Form component MUST receive `form` prop from parent:**
199
+
200
+ ```typescript
201
+ // Form component
202
+ interface UserFormProps {
203
+ form: FormInstance; // ← REQUIRED
204
+ onSubmit: (values: UserCreate) => void;
205
+ // ...
206
+ }
207
+
208
+ export function UserForm({ form, onSubmit, ... }: UserFormProps) {
209
+ return (
210
+ <Form form={form} onFinish={onSubmit}>
211
+ ...
212
+ </Form>
213
+ );
214
+ }
215
+ ```
216
+
217
+ **Page creates form and handles backend errors:**
218
+
219
+ ```typescript
220
+ // Page component
221
+ export default function NewUserPage() {
222
+ const [form] = Form.useForm(); // ← Create in parent
223
+
224
+ const mutation = useMutation({
225
+ mutationFn: userService.create,
226
+ onSuccess: () => { ... },
227
+ onError: (error) => {
228
+ form.setFields(getFormErrors(error)); // ← Set errors from backend
229
+ },
230
+ });
231
+
232
+ return (
233
+ <UserForm
234
+ form={form} // ← Pass to form component
235
+ onSubmit={(values) => mutation.mutate(values)}
236
+ />
237
+ );
238
+ }
239
+ ```
240
+
241
+ **Helper `getFormErrors`** (available in `lib/api.ts`):
242
+
243
+ ```typescript
244
+ import { getFormErrors } from "@/lib/api";
245
+
246
+ // Converts Laravel 422 response to Ant Design format:
247
+ // { errors: { email: ["Email already exists"] } }
248
+ // → [{ name: "email", errors: ["Email already exists"] }]
249
+ ```
250
+
251
+ **Rules:**
252
+ 1. Form component does NOT create `Form.useForm()` - receives from parent
253
+ 2. Page creates form instance and passes down
254
+ 3. `onError` calls `form.setFields(getFormErrors(error))`