@famgia/omnify-typescript 0.0.68 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@famgia/omnify-typescript",
3
- "version": "0.0.68",
3
+ "version": "0.0.69",
4
4
  "description": "TypeScript type definitions generator for Omnify schemas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -31,7 +31,6 @@
31
31
  "files": [
32
32
  "dist",
33
33
  "scripts",
34
- "ai-guides",
35
34
  "stubs"
36
35
  ],
37
36
  "keywords": [
@@ -49,7 +48,7 @@
49
48
  "directory": "packages/typescript-generator"
50
49
  },
51
50
  "dependencies": {
52
- "@famgia/omnify-types": "0.0.78"
51
+ "@famgia/omnify-types": "0.0.79"
53
52
  },
54
53
  "peerDependencies": {
55
54
  "zod": "^3.0.0"
@@ -71,82 +71,31 @@ export interface UserCreateInput { ... } // WRONG
71
71
 
72
72
  ## Form Pattern (Omnify)
73
73
 
74
- ### Imports
74
+ > **Detailed Guide:** See `react-form.mdc` for complete form development guide.
75
+
76
+ **Quick Reference:**
75
77
 
76
78
  ```typescript
77
- import { Form, Input, Button, Space, Card, Divider } from 'antd';
78
- import type { FormInstance } from 'antd';
79
+ // 1. Imports
79
80
  import { zodRule, setZodLocale } from '@/omnify/lib';
80
81
  import { OmnifyForm } from '@/omnify/components';
81
- import {
82
- type Customer,
83
- type CustomerCreate,
84
- customerSchemas,
85
- customerI18n,
86
- getCustomerFieldLabel,
87
- getCustomerFieldPlaceholder,
88
- } from '@/omnify/schemas';
89
- ```
82
+ import { customerSchemas, customerI18n, getCustomerFieldLabel } from '@/omnify/schemas';
90
83
 
91
- ### Helper Functions (MUST define)
92
-
93
- ```typescript
84
+ // 2. Helper functions (MUST define in every form)
94
85
  const LOCALE = 'ja';
95
- setZodLocale(LOCALE);
96
-
97
86
  const label = (key: string) => getCustomerFieldLabel(key, LOCALE);
98
- const placeholder = (key: string) => getCustomerFieldPlaceholder(key, LOCALE);
99
- const rule = (key: keyof typeof customerSchemas) =>
100
- zodRule(customerSchemas[key], label(key));
101
- ```
87
+ const rule = (key: keyof typeof customerSchemas) => zodRule(customerSchemas[key], label(key));
102
88
 
103
- ### Form.Item Pattern
104
-
105
- ```typescript
106
- <Form.Item
107
- name="email"
108
- label={label('email')}
109
- rules={[rule('email')]}
110
- >
111
- <Input type="email" placeholder={placeholder('email')} />
89
+ // 3. Form.Item pattern
90
+ <Form.Item name="email" label={label('email')} rules={[rule('email')]}>
91
+ <Input />
112
92
  </Form.Item>
113
- ```
114
-
115
- ### Compound Fields (Japan Plugin)
116
93
 
117
- ```typescript
118
- // Japanese Name (4 fields)
119
- <OmnifyForm.JapaneseName
120
- schemas={customerSchemas}
121
- i18n={customerI18n}
122
- prefix="name"
123
- required
124
- />
125
-
126
- // Japanese Address (5 fields)
127
- <OmnifyForm.JapaneseAddress
128
- form={form}
129
- schemas={customerSchemas}
130
- i18n={customerI18n}
131
- prefix="address"
132
- />
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" />
133
97
  ```
134
98
 
135
- ### Key Files
136
-
137
- | File | Purpose |
138
- | ------------------------ | ----------------------------- |
139
- | `@/omnify/schemas` | Types, schemas, i18n (generated) |
140
- | `@/omnify/lib` | `zodRule()`, `setZodLocale()` |
141
- | `@/omnify/components` | `OmnifyForm.*` components |
142
-
143
- ### Rules
144
-
145
- 1. **Import from `@/omnify/*`** - All Omnify utilities
146
- 2. **Define helpers** - `label()`, `placeholder()`, `rule()`
147
- 3. **Use `Divider`** - For form sections
148
- 4. **Use `label()` helper** - Get from `getUserPropertyDisplayName()`
149
-
150
99
  ## File Location
151
100
 
152
101
  | What | Where |
@@ -1,401 +0,0 @@
1
- # Omnify + Ant Design Integration Guide
2
-
3
- This guide shows how to use Omnify-generated validation rules with Ant Design Forms.
4
-
5
- ## Generated Files
6
-
7
- Omnify generates validation rules in `rules/` directory:
8
- - `rules/{Model}.rules.ts` - Validation rules for each model
9
-
10
- ## File Structure
11
-
12
- ```typescript
13
- // rules/User.rules.ts
14
-
15
- // Display name for model (multi-locale)
16
- export const UserDisplayName: LocaleMap = {
17
- ja: 'ユーザー',
18
- en: 'User',
19
- };
20
-
21
- // Display names for properties (multi-locale)
22
- export const UserPropertyDisplayNames: Record<string, LocaleMap> = {
23
- name: { ja: '名前', en: 'Name' },
24
- email: { ja: 'メールアドレス', en: 'Email' },
25
- };
26
-
27
- // Validation rules with multi-locale messages
28
- export const UserRules: Record<string, ValidationRule[]> = {
29
- name: [
30
- { required: true, message: { ja: '名前は必須です', en: 'Name is required' } },
31
- { max: 100, message: { ja: '名前は100文字以内で入力してください', en: 'Name must be at most 100 characters' } },
32
- ],
33
- email: [
34
- { required: true, message: { ja: 'メールアドレスは必須です', en: 'Email is required' } },
35
- { type: 'email', message: { ja: 'メールアドレスの形式が正しくありません', en: 'Email is not a valid email address' } },
36
- ],
37
- };
38
-
39
- // Helper functions
40
- export function getUserRules(locale: string): Record<string, Rule[]>;
41
- export function getUserDisplayName(locale: string): string;
42
- export function getUserPropertyDisplayName(property: string, locale: string): string;
43
- ```
44
-
45
- ## Basic Usage
46
-
47
- ```tsx
48
- import { Form, Input, Button } from 'antd';
49
- import {
50
- getUserRules,
51
- getUserDisplayName,
52
- getUserPropertyDisplayName
53
- } from '@/types/model/rules/User.rules';
54
-
55
- interface UserFormProps {
56
- locale?: string;
57
- onSubmit: (values: User) => void;
58
- }
59
-
60
- export function UserForm({ locale = 'ja', onSubmit }: UserFormProps) {
61
- const [form] = Form.useForm();
62
- const rules = getUserRules(locale);
63
-
64
- return (
65
- <Form
66
- form={form}
67
- layout="vertical"
68
- onFinish={onSubmit}
69
- >
70
- <Form.Item
71
- name="name"
72
- label={getUserPropertyDisplayName('name', locale)}
73
- rules={rules.name}
74
- >
75
- <Input placeholder={getUserPropertyDisplayName('name', locale)} />
76
- </Form.Item>
77
-
78
- <Form.Item
79
- name="email"
80
- label={getUserPropertyDisplayName('email', locale)}
81
- rules={rules.email}
82
- >
83
- <Input type="email" />
84
- </Form.Item>
85
-
86
- <Form.Item>
87
- <Button type="primary" htmlType="submit">
88
- {locale === 'ja' ? '送信' : 'Submit'}
89
- </Button>
90
- </Form.Item>
91
- </Form>
92
- );
93
- }
94
- ```
95
-
96
- ## With Edit Mode (Initial Values)
97
-
98
- ```tsx
99
- import { Form, Input, Button, Spin } from 'antd';
100
- import { getUserRules, getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
101
- import { User } from '@/types/model';
102
-
103
- interface UserEditFormProps {
104
- user: User;
105
- locale?: string;
106
- onSubmit: (values: Partial<User>) => void;
107
- loading?: boolean;
108
- }
109
-
110
- export function UserEditForm({ user, locale = 'ja', onSubmit, loading }: UserEditFormProps) {
111
- const [form] = Form.useForm();
112
- const rules = getUserRules(locale);
113
-
114
- // Set initial values when user data changes
115
- React.useEffect(() => {
116
- form.setFieldsValue(user);
117
- }, [user, form]);
118
-
119
- return (
120
- <Spin spinning={loading}>
121
- <Form
122
- form={form}
123
- layout="vertical"
124
- initialValues={user}
125
- onFinish={onSubmit}
126
- >
127
- <Form.Item
128
- name="name"
129
- label={getUserPropertyDisplayName('name', locale)}
130
- rules={rules.name}
131
- >
132
- <Input />
133
- </Form.Item>
134
-
135
- <Form.Item
136
- name="email"
137
- label={getUserPropertyDisplayName('email', locale)}
138
- rules={rules.email}
139
- >
140
- <Input type="email" />
141
- </Form.Item>
142
-
143
- <Form.Item>
144
- <Button type="primary" htmlType="submit" loading={loading}>
145
- {locale === 'ja' ? '更新' : 'Update'}
146
- </Button>
147
- </Form.Item>
148
- </Form>
149
- </Spin>
150
- );
151
- }
152
- ```
153
-
154
- ## Dynamic Locale Switching
155
-
156
- ```tsx
157
- import { Form, Input, Select } from 'antd';
158
- import { useState, useMemo } from 'react';
159
- import { getUserRules, getUserPropertyDisplayName, getUserDisplayName } from '@/types/model/rules/User.rules';
160
-
161
- export function UserFormWithLocale() {
162
- const [locale, setLocale] = useState('ja');
163
- const [form] = Form.useForm();
164
-
165
- // Memoize rules to avoid recalculation
166
- const rules = useMemo(() => getUserRules(locale), [locale]);
167
-
168
- return (
169
- <>
170
- <Select value={locale} onChange={setLocale} style={{ marginBottom: 16 }}>
171
- <Select.Option value="ja">日本語</Select.Option>
172
- <Select.Option value="en">English</Select.Option>
173
- <Select.Option value="vi">Tiếng Việt</Select.Option>
174
- </Select>
175
-
176
- <h2>{getUserDisplayName(locale)}</h2>
177
-
178
- <Form form={form} layout="vertical">
179
- <Form.Item
180
- name="name"
181
- label={getUserPropertyDisplayName('name', locale)}
182
- rules={rules.name}
183
- >
184
- <Input />
185
- </Form.Item>
186
-
187
- <Form.Item
188
- name="email"
189
- label={getUserPropertyDisplayName('email', locale)}
190
- rules={rules.email}
191
- >
192
- <Input type="email" />
193
- </Form.Item>
194
- </Form>
195
- </>
196
- );
197
- }
198
- ```
199
-
200
- ## With Table Columns
201
-
202
- ```tsx
203
- import { Table, TableColumnsType } from 'antd';
204
- import { getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
205
- import { User } from '@/types/model';
206
-
207
- interface UserTableProps {
208
- users: User[];
209
- locale?: string;
210
- loading?: boolean;
211
- }
212
-
213
- export function UserTable({ users, locale = 'ja', loading }: UserTableProps) {
214
- const columns: TableColumnsType<User> = [
215
- {
216
- title: getUserPropertyDisplayName('name', locale),
217
- dataIndex: 'name',
218
- key: 'name',
219
- },
220
- {
221
- title: getUserPropertyDisplayName('email', locale),
222
- dataIndex: 'email',
223
- key: 'email',
224
- },
225
- ];
226
-
227
- return (
228
- <Table
229
- columns={columns}
230
- dataSource={users}
231
- rowKey="id"
232
- loading={loading}
233
- />
234
- );
235
- }
236
- ```
237
-
238
- ## Custom Form Hook
239
-
240
- ```tsx
241
- import { Form, FormInstance } from 'antd';
242
- import { useMemo } from 'react';
243
-
244
- // Generic hook for any model's rules
245
- export function useModelForm<T>(
246
- getRules: (locale: string) => Record<string, any[]>,
247
- locale: string = 'ja'
248
- ): {
249
- form: FormInstance<T>;
250
- rules: Record<string, any[]>;
251
- } {
252
- const [form] = Form.useForm<T>();
253
- const rules = useMemo(() => getRules(locale), [getRules, locale]);
254
-
255
- return { form, rules };
256
- }
257
-
258
- // Usage
259
- import { getUserRules } from '@/types/model/rules/User.rules';
260
-
261
- function MyComponent() {
262
- const { form, rules } = useModelForm(getUserRules, 'ja');
263
-
264
- return (
265
- <Form form={form}>
266
- <Form.Item name="name" rules={rules.name}>
267
- <Input />
268
- </Form.Item>
269
- </Form>
270
- );
271
- }
272
- ```
273
-
274
- ## Modal Form
275
-
276
- ```tsx
277
- import { Modal, Form, Input, message } from 'antd';
278
- import { getUserRules, getUserDisplayName, getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
279
- import { User } from '@/types/model';
280
-
281
- interface UserModalFormProps {
282
- open: boolean;
283
- onClose: () => void;
284
- onSubmit: (values: Partial<User>) => Promise<void>;
285
- initialValues?: Partial<User>;
286
- locale?: string;
287
- }
288
-
289
- export function UserModalForm({
290
- open,
291
- onClose,
292
- onSubmit,
293
- initialValues,
294
- locale = 'ja'
295
- }: UserModalFormProps) {
296
- const [form] = Form.useForm();
297
- const [loading, setLoading] = useState(false);
298
- const rules = getUserRules(locale);
299
-
300
- const handleSubmit = async () => {
301
- try {
302
- const values = await form.validateFields();
303
- setLoading(true);
304
- await onSubmit(values);
305
- message.success(locale === 'ja' ? '保存しました' : 'Saved successfully');
306
- form.resetFields();
307
- onClose();
308
- } catch (error) {
309
- if (error instanceof Error) {
310
- message.error(error.message);
311
- }
312
- } finally {
313
- setLoading(false);
314
- }
315
- };
316
-
317
- return (
318
- <Modal
319
- title={getUserDisplayName(locale)}
320
- open={open}
321
- onCancel={onClose}
322
- onOk={handleSubmit}
323
- confirmLoading={loading}
324
- okText={locale === 'ja' ? '保存' : 'Save'}
325
- cancelText={locale === 'ja' ? 'キャンセル' : 'Cancel'}
326
- >
327
- <Form
328
- form={form}
329
- layout="vertical"
330
- initialValues={initialValues}
331
- >
332
- <Form.Item
333
- name="name"
334
- label={getUserPropertyDisplayName('name', locale)}
335
- rules={rules.name}
336
- >
337
- <Input />
338
- </Form.Item>
339
-
340
- <Form.Item
341
- name="email"
342
- label={getUserPropertyDisplayName('email', locale)}
343
- rules={rules.email}
344
- >
345
- <Input type="email" />
346
- </Form.Item>
347
- </Form>
348
- </Modal>
349
- );
350
- }
351
- ```
352
-
353
- ## Validation Rule Types
354
-
355
- Omnify generates these rule types based on schema:
356
-
357
- | Schema | Generated Rule |
358
- |--------|---------------|
359
- | `required: true` | `{ required: true, message: {...} }` |
360
- | `type: Email` | `{ type: 'email', message: {...} }` |
361
- | `maxLength: N` | `{ max: N, message: {...} }` |
362
- | `minLength: N` | `{ min: N, message: {...} }` |
363
- | `max: N` (numeric) | `{ max: N, type: 'number', message: {...} }` |
364
- | `min: N` (numeric) | `{ min: N, type: 'number', message: {...} }` |
365
- | `pattern: regex` | `{ pattern: /regex/, message: {...} }` |
366
-
367
- ## Built-in Validation Messages
368
-
369
- Omnify includes templates for 5 languages:
370
- - Japanese (ja)
371
- - English (en)
372
- - Vietnamese (vi)
373
- - Korean (ko)
374
- - Chinese (zh)
375
-
376
- Custom templates can be configured in `omnify.config.ts`:
377
-
378
- ```typescript
379
- export default defineConfig({
380
- output: {
381
- typescript: {
382
- validationTemplates: {
383
- required: {
384
- ja: '${displayName}を入力してください',
385
- en: '${displayName} is required',
386
- vi: '${displayName} là bắt buộc',
387
- },
388
- maxLength: {
389
- ja: '${displayName}は${max}文字以内です',
390
- en: '${displayName} must be at most ${max} characters',
391
- },
392
- },
393
- },
394
- },
395
- });
396
- ```
397
-
398
- Available placeholders:
399
- - `${displayName}` - Property display name
400
- - `${min}` - Minimum value/length
401
- - `${max}` - Maximum value/length
@@ -1,259 +0,0 @@
1
- # Omnify React Form Guide
2
-
3
- This guide explains how to build forms using Omnify generated schemas with **Ant Design** and **TanStack Query**.
4
-
5
- ## Quick Start
6
-
7
- ```tsx
8
- import { Form } from 'antd';
9
- import { useFormMutation } from '@/omnify/hooks';
10
- import { type CustomerCreate } from '@/omnify/schemas';
11
- import { CustomerForm } from '@/features/customers';
12
- import { api } from '@/lib/api';
13
-
14
- function CreateCustomerPage() {
15
- const [form] = Form.useForm<CustomerCreate>();
16
-
17
- const mutation = useFormMutation<CustomerCreate>({
18
- form,
19
- mutationFn: (data) => api.post('/customers', data),
20
- invalidateKeys: [['customers']],
21
- successMessage: '顧客を登録しました',
22
- onSuccess: () => form.resetFields(),
23
- });
24
-
25
- return (
26
- <CustomerForm
27
- form={form}
28
- onSubmit={mutation.mutate}
29
- loading={mutation.isPending}
30
- />
31
- );
32
- }
33
- ```
34
-
35
- ## useFormMutation Hook
36
-
37
- Location: `resources/ts/omnify/hooks/use-form-mutation.ts`
38
-
39
- ### Usage with Types
40
-
41
- ```tsx
42
- import { useFormMutation } from '@/omnify/hooks';
43
- import { type CustomerCreate } from '@/omnify/schemas';
44
-
45
- // With type parameter for type safety
46
- const mutation = useFormMutation<CustomerCreate>({
47
- form,
48
- mutationFn: (data) => api.post('/customers', data),
49
- invalidateKeys: [['customers']],
50
- successMessage: '保存しました',
51
- onSuccess: () => form.resetFields(),
52
- });
53
- ```
54
-
55
- ### Options
56
-
57
- | Option | Type | Description |
58
- |--------|------|-------------|
59
- | `form` | `FormInstance<T>` | Ant Design form instance (required) |
60
- | `mutationFn` | `(data: T) => Promise` | API call function (required) |
61
- | `invalidateKeys` | `string[][]` | Query keys to invalidate on success |
62
- | `successMessage` | `string` | Toast message on success |
63
- | `onSuccess` | `(data) => void` | Callback after success |
64
- | `onError` | `(error) => void` | Callback after error |
65
-
66
- ### Laravel Error Handling
67
-
68
- The hook automatically handles Laravel validation errors (422):
69
-
70
- ```json
71
- {
72
- "message": "The given data was invalid.",
73
- "errors": {
74
- "email": ["The email has already been taken."]
75
- }
76
- }
77
- ```
78
-
79
- ## Japanese Form Components
80
-
81
- Omnify provides specialized components for Japanese data input.
82
-
83
- ### JapaneseNameField
84
-
85
- ```tsx
86
- import { JapaneseNameField } from '@/omnify/components';
87
-
88
- <JapaneseNameField
89
- schemas={customerSchemas}
90
- i18n={customerI18n}
91
- prefix="name" // name_lastname, name_firstname, etc.
92
- required // Show required asterisk
93
- showKana={true} // Show kana fields (default)
94
- />
95
- ```
96
-
97
- **Fields:** `{prefix}_lastname`, `{prefix}_firstname`, `{prefix}_kana_lastname`, `{prefix}_kana_firstname`
98
-
99
- ### JapaneseAddressField
100
-
101
- ```tsx
102
- import { JapaneseAddressField } from '@/omnify/components';
103
-
104
- <JapaneseAddressField
105
- form={form}
106
- schemas={customerSchemas}
107
- i18n={customerI18n}
108
- prefix="address"
109
- enablePostalLookup={true} // Postal code → address lookup
110
- />
111
- ```
112
-
113
- **Fields:** `{prefix}_postal_code`, `{prefix}_prefecture`, `{prefix}_address1`, `{prefix}_address2`, `{prefix}_address3`
114
-
115
- **Options:**
116
- - `enablePostalLookup` - Enable postal code search
117
- - `autoSearch` - Auto-search when 7 digits entered
118
- - `usePrefectureId` - Use numeric ID instead of string enum
119
-
120
- ### JapaneseBankField
121
-
122
- ```tsx
123
- import { JapaneseBankField } from '@/omnify/components';
124
-
125
- <JapaneseBankField
126
- schemas={customerSchemas}
127
- i18n={customerI18n}
128
- prefix="bank"
129
- />
130
- ```
131
-
132
- **Fields:** `{prefix}_bank_code`, `{prefix}_branch_code`, `{prefix}_account_type`, `{prefix}_account_number`, `{prefix}_account_holder`
133
-
134
- Account type options auto-loaded from `BankAccountType` enum with i18n.
135
-
136
- ## Complete Form Example
137
-
138
- ```tsx
139
- import { Form, Input, Button, Card, Space, Divider } from 'antd';
140
- import type { FormInstance } from 'antd';
141
- import { zodRule, setZodLocale } from '@/omnify/lib';
142
- import { JapaneseNameField, JapaneseAddressField, JapaneseBankField } from '@/omnify/components';
143
- import {
144
- type Customer, type CustomerCreate,
145
- customerSchemas, customerI18n,
146
- getCustomerFieldLabel, getCustomerFieldPlaceholder,
147
- } from '@/omnify/schemas';
148
-
149
- const LOCALE = 'ja';
150
-
151
- interface CustomerFormProps {
152
- form: FormInstance<CustomerCreate>;
153
- initialValues?: Partial<Customer>;
154
- onSubmit: (values: CustomerCreate) => void;
155
- loading?: boolean;
156
- isEdit?: boolean;
157
- }
158
-
159
- export function CustomerForm({ form, initialValues, onSubmit, loading, isEdit }: CustomerFormProps) {
160
- setZodLocale(LOCALE);
161
-
162
- const label = (key: string) => getCustomerFieldLabel(key, LOCALE);
163
- const placeholder = (key: string) => getCustomerFieldPlaceholder(key, LOCALE);
164
- const rule = (key: keyof typeof customerSchemas) => zodRule(customerSchemas[key], label(key));
165
-
166
- return (
167
- <Card>
168
- <Form form={form} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}
169
- initialValues={initialValues} onFinish={onSubmit} style={{ maxWidth: 800 }}>
170
-
171
- <Divider orientation="left">氏名</Divider>
172
- <JapaneseNameField schemas={customerSchemas} i18n={customerI18n} prefix="name" required />
173
-
174
- <Divider orientation="left">連絡先</Divider>
175
- <Form.Item name="phone" label={label('phone')} rules={[rule('phone')]}>
176
- <Input placeholder={placeholder('phone')} />
177
- </Form.Item>
178
- <Form.Item name="email" label={label('email')} rules={[rule('email')]}>
179
- <Input type="email" placeholder={placeholder('email')} />
180
- </Form.Item>
181
-
182
- <Divider orientation="left">住所</Divider>
183
- <JapaneseAddressField form={form} schemas={customerSchemas} i18n={customerI18n}
184
- prefix="address" enablePostalLookup={true} />
185
-
186
- <Divider orientation="left">銀行口座</Divider>
187
- <JapaneseBankField schemas={customerSchemas} i18n={customerI18n} prefix="bank" />
188
-
189
- <Form.Item wrapperCol={{ offset: 6, span: 18 }}>
190
- <Space>
191
- <Button type="primary" htmlType="submit" loading={loading}>
192
- {isEdit ? '更新' : '登録'}
193
- </Button>
194
- </Space>
195
- </Form.Item>
196
- </Form>
197
- </Card>
198
- );
199
- }
200
- ```
201
-
202
- ## Zod Validation with i18n
203
-
204
- ```tsx
205
- import { zodRule, setZodLocale } from '@/omnify/lib';
206
-
207
- // Set locale once at component level
208
- setZodLocale('ja');
209
-
210
- // Use zodRule for field validation
211
- <Form.Item name="email" label={label('email')} rules={[zodRule(customerSchemas.email, label('email'))]}>
212
- <Input />
213
- </Form.Item>
214
- ```
215
-
216
- ## Kana Validation Rules
217
-
218
- ```tsx
219
- import { kanaString, KATAKANA_PATTERN } from '@/omnify/lib/rules';
220
-
221
- // Full-width katakana (default)
222
- const kanaSchema = kanaString();
223
-
224
- // Custom options
225
- const kanaSchema = kanaString({
226
- fullWidthKatakana: true,
227
- hiragana: true,
228
- allowNumbers: true,
229
- });
230
- ```
231
-
232
- ## File Structure
233
-
234
- ```
235
- resources/ts/omnify/
236
- ├── components/
237
- │ ├── JapaneseNameField.tsx
238
- │ ├── JapaneseAddressField.tsx
239
- │ └── JapaneseBankField.tsx
240
- ├── enum/
241
- │ └── plugin/
242
- │ ├── Prefecture.ts
243
- │ └── BankAccountType.ts
244
- ├── hooks/
245
- │ └── use-form-mutation.ts
246
- ├── lib/
247
- │ ├── form-validation.ts
248
- │ ├── zod-i18n.ts
249
- │ └── rules/kana.ts
250
- └── schemas/
251
- ```
252
-
253
- ## Tips
254
-
255
- 1. **Use type generics** - `useFormMutation<CustomerCreate>` for type safety
256
- 2. **Use `setZodLocale`** - Call once for localized validation messages
257
- 3. **Use Japanese* components** - Built-in i18n, validation, postal lookup
258
- 4. **Set `invalidateKeys`** - Auto-refresh lists after mutations
259
- 5. **Don't pass `form` to Name/Bank components** - Only Address needs it for postal lookup
@@ -1,363 +0,0 @@
1
- # Omnify TypeScript Generator Guide
2
-
3
- This guide covers TypeScript-specific features and generated code patterns for Omnify.
4
-
5
- ## Quick Start
6
-
7
- ```bash
8
- # Create new project (recommended)
9
- npx @famgia/omnify create-laravel-project my-app
10
- cd my-app
11
-
12
- # Or initialize in existing project
13
- npx @famgia/omnify init
14
-
15
- # Generate TypeScript types
16
- npx @famgia/omnify generate
17
- ```
18
-
19
- ## Generated Files
20
-
21
- When you run `npx @famgia/omnify generate`, the following TypeScript files are generated:
22
-
23
- - `base/*.ts` - Base model interfaces
24
- - `enum/*.ts` - Enum types with multi-locale labels
25
- - `rules/*.ts` - Ant Design compatible validation rules
26
-
27
- ## Type Generation
28
-
29
- ### Object Schema → Interface
30
-
31
- ```yaml
32
- # yaml-language-server: $schema=./node_modules/.omnify/combined-schema.json
33
- name: User
34
- properties:
35
- id:
36
- type: BigInt
37
- required: true
38
- name:
39
- type: String
40
- required: true
41
- maxLength: 255
42
- email:
43
- type: String
44
- required: true
45
- unique: true
46
- profile:
47
- type: Json
48
- createdAt:
49
- type: DateTime
50
- ```
51
-
52
- Generated:
53
- ```typescript
54
- export interface User {
55
- id: number;
56
- name: string;
57
- email: string;
58
- profile: Record<string, unknown> | null;
59
- createdAt: Date | null;
60
- }
61
- ```
62
-
63
- ## Type Mapping
64
-
65
- | Schema Type | TypeScript Type |
66
- |-------------|-----------------|
67
- | `String` | `string` |
68
- | `Text` | `string` |
69
- | `MediumText` | `string` |
70
- | `LongText` | `string` |
71
- | `TinyInt` | `number` |
72
- | `Int` | `number` |
73
- | `BigInt` | `number` |
74
- | `Float` | `number` |
75
- | `Decimal` | `number` |
76
- | `Boolean` | `boolean` |
77
- | `Date` | `Date` |
78
- | `DateTime` | `Date` |
79
- | `Timestamp` | `Date` |
80
- | `Json` | `Record<string, unknown>` |
81
- | `EnumRef` | Generated enum type |
82
- | `Association` | Related model type / array |
83
-
84
- ## Hidden Schemas
85
-
86
- Schemas with `options.hidden: true` are skipped for TypeScript generation:
87
-
88
- ```yaml
89
- name: AppCache
90
- options:
91
- hidden: true # No TypeScript interface generated
92
- ```
93
-
94
- Use cases:
95
- - System tables (cache, sessions, jobs)
96
- - Tables that don't need frontend types
97
-
98
- ## Enum Generation (Multi-locale)
99
-
100
- ```yaml
101
- # schemas/PostStatus.yaml
102
- name: PostStatus
103
- kind: enum
104
- displayName:
105
- ja: 投稿ステータス
106
- en: Post Status
107
- values:
108
- draft:
109
- ja: 下書き
110
- en: Draft
111
- published:
112
- ja: 公開済み
113
- en: Published
114
- archived:
115
- ja: アーカイブ
116
- en: Archived
117
- ```
118
-
119
- Generated:
120
- ```typescript
121
- export const PostStatus = {
122
- draft: 'draft',
123
- published: 'published',
124
- archived: 'archived',
125
- } as const;
126
-
127
- export type PostStatus = typeof PostStatus[keyof typeof PostStatus];
128
-
129
- // Multi-locale labels
130
- export const PostStatusLabels: Record<PostStatus, Record<string, string>> = {
131
- draft: { ja: '下書き', en: 'Draft' },
132
- published: { ja: '公開済み', en: 'Published' },
133
- archived: { ja: 'アーカイブ', en: 'Archived' },
134
- };
135
-
136
- // Get label for specific locale
137
- export function getPostStatusLabel(value: PostStatus, locale: string = 'en'): string {
138
- return PostStatusLabels[value]?.[locale] ?? PostStatusLabels[value]?.['en'] ?? value;
139
- }
140
-
141
- // Helper functions
142
- export const PostStatusValues = Object.values(PostStatus);
143
- export function isPostStatus(value: unknown): value is PostStatus {
144
- return PostStatusValues.includes(value as PostStatus);
145
- }
146
- ```
147
-
148
- ## Display Names and Placeholders
149
-
150
- Omnify generates multi-locale display names and placeholders for form inputs.
151
-
152
- ```typescript
153
- import {
154
- UserDisplayName,
155
- UserPropertyDisplayNames,
156
- UserPropertyPlaceholders,
157
- getUserDisplayName,
158
- getUserPropertyDisplayName,
159
- getUserPropertyPlaceholder,
160
- } from './types/models/User';
161
-
162
- // Get model display name
163
- getUserDisplayName('ja'); // "ユーザー"
164
- getUserDisplayName('en'); // "User"
165
-
166
- // Get property display name
167
- getUserPropertyDisplayName('email', 'ja'); // "メールアドレス"
168
- getUserPropertyDisplayName('email', 'en'); // "Email"
169
-
170
- // Get property placeholder
171
- getUserPropertyPlaceholder('email', 'ja'); // "メールアドレスを入力"
172
- getUserPropertyPlaceholder('email', 'en'); // "Enter your email"
173
- ```
174
-
175
- ### Using Placeholders in Forms
176
-
177
- ```tsx
178
- import { Form, Input } from 'antd';
179
- import {
180
- getUserRules,
181
- getUserPropertyDisplayName,
182
- getUserPropertyPlaceholder,
183
- } from './types/models/User';
184
-
185
- function UserForm({ locale = 'ja' }) {
186
- const rules = getUserRules(locale);
187
- return (
188
- <Form>
189
- <Form.Item
190
- name="email"
191
- label={getUserPropertyDisplayName('email', locale)}
192
- rules={rules.email}
193
- >
194
- <Input placeholder={getUserPropertyPlaceholder('email', locale)} />
195
- </Form.Item>
196
- </Form>
197
- );
198
- }
199
- ```
200
-
201
- ## Validation Rules (Ant Design)
202
-
203
- Omnify generates Ant Design compatible validation rules in `rules/` directory.
204
-
205
- **See detailed guide:** `.claude/omnify/antdesign-guide.md`
206
-
207
- Quick example:
208
- ```tsx
209
- import { Form, Input } from 'antd';
210
- import { getUserRules, getUserPropertyDisplayName } from './types/model/rules/User.rules';
211
-
212
- function UserForm({ locale = 'ja' }) {
213
- const rules = getUserRules(locale);
214
- return (
215
- <Form>
216
- <Form.Item name="name" label={getUserPropertyDisplayName('name', locale)} rules={rules.name}>
217
- <Input />
218
- </Form.Item>
219
- </Form>
220
- );
221
- }
222
- ```
223
-
224
- ## Association Types
225
-
226
- ### ManyToOne
227
- ```yaml
228
- author:
229
- type: Association
230
- relation: ManyToOne
231
- target: User
232
- ```
233
-
234
- Generated:
235
- ```typescript
236
- export interface Post {
237
- authorId: number;
238
- author?: User; // Optional: loaded relation
239
- }
240
- ```
241
-
242
- ### OneToMany
243
- ```yaml
244
- posts:
245
- type: Association
246
- relation: OneToMany
247
- target: Post
248
- ```
249
-
250
- Generated:
251
- ```typescript
252
- export interface User {
253
- posts?: Post[]; // Optional: loaded relation array
254
- }
255
- ```
256
-
257
- ### ManyToMany
258
- ```yaml
259
- tags:
260
- type: Association
261
- relation: ManyToMany
262
- target: Tag
263
- ```
264
-
265
- Generated:
266
- ```typescript
267
- export interface Post {
268
- tags?: Tag[]; // Optional: loaded relation array
269
- }
270
- ```
271
-
272
- ## Nullable Fields
273
-
274
- Fields without `required: true` are nullable:
275
-
276
- ```yaml
277
- description:
278
- type: LongText # No required: true
279
- ```
280
-
281
- Generated:
282
- ```typescript
283
- description: string | null;
284
- ```
285
-
286
- ## Using Generated Types
287
-
288
- ```typescript
289
- import { User, Post, PostStatus, isPostStatus } from './types/omnify-types';
290
-
291
- // Type-safe object creation
292
- const user: User = {
293
- id: 1,
294
- name: 'John',
295
- email: 'john@example.com',
296
- profile: null,
297
- createdAt: new Date(),
298
- };
299
-
300
- // Enum usage
301
- const status: PostStatus = PostStatus.draft;
302
-
303
- // Type guard
304
- function handleStatus(value: unknown) {
305
- if (isPostStatus(value)) {
306
- console.log('Valid status:', value);
307
- }
308
- }
309
- ```
310
-
311
- ## Commands
312
-
313
- ```bash
314
- # Create new project
315
- npx @famgia/omnify create-laravel-project my-app
316
-
317
- # Generate TypeScript types
318
- npx @famgia/omnify generate
319
-
320
- # Force regenerate all files
321
- npx @famgia/omnify generate --force
322
-
323
- # Only generate TypeScript types
324
- npx @famgia/omnify generate --types-only
325
-
326
- # Validate schemas
327
- npx @famgia/omnify validate
328
- ```
329
-
330
- ## Configuration
331
-
332
- ```typescript
333
- // omnify.config.ts
334
- import { defineConfig } from '@famgia/omnify';
335
- import typescript from '@famgia/omnify-typescript/plugin';
336
-
337
- export default defineConfig({
338
- schemasDir: './schemas',
339
- lockFilePath: './.omnify.lock',
340
-
341
- plugins: [
342
- typescript({
343
- // Output path for TypeScript files
344
- path: './resources/ts/types/models',
345
-
346
- // Generate Ant Design validation rules
347
- generateRules: true,
348
- }),
349
- ],
350
-
351
- locale: {
352
- locales: ['ja', 'en'],
353
- defaultLocale: 'ja',
354
- },
355
- });
356
- ```
357
-
358
- ### Configuration Options
359
-
360
- | Option | Type | Default | Description |
361
- |--------|------|---------|-------------|
362
- | `path` | `string` | `./src/types/model` | TypeScript output directory |
363
- | `generateRules` | `boolean` | `true` | Generate Ant Design validation rules |