@famgia/omnify-typescript 0.0.68 → 0.0.70
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.
|
|
3
|
+
"version": "0.0.70",
|
|
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.
|
|
51
|
+
"@famgia/omnify-types": "0.0.80"
|
|
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
|
-
|
|
74
|
+
> **Detailed Guide:** See `react-form.mdc` for complete form development guide.
|
|
75
|
+
|
|
76
|
+
**Quick Reference:**
|
|
75
77
|
|
|
76
78
|
```typescript
|
|
77
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
<OmnifyForm.
|
|
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 |
|