@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,524 @@
1
+ # TypeScript Types Guide
2
+
3
+ > This guide defines where and how to define types in this project.
4
+
5
+ ## Type Categories
6
+
7
+ | Category | Location | Generated | Example |
8
+ | ------------------- | -------------------- | --------- | ----------------------------- |
9
+ | **Model** | `@/types/model` | ✅ Omnify | `User`, `Post` |
10
+ | **Create/Update** | `@/types/model` | ✅ Omnify | `UserCreate`, `UserUpdate` |
11
+ | **Common** | `@/types/model` | ✅ Omnify | `DateTimeString`, `LocaleMap` |
12
+ | **Validation** | `@/types/model` | ✅ Omnify | `getUserRules(locale)` |
13
+ | **Enum** | `@/types/model/enum` | ✅ Omnify | `PostStatus`, `UserRole` |
14
+ | **API Params** | Service file | ❌ Manual | `UserListParams` |
15
+ | **API Response** | `@/lib/api.ts` | ❌ Manual | `PaginatedResponse<T>` |
16
+ | **Component Props** | Component file | ❌ Manual | `UserTableProps` |
17
+
18
+ ---
19
+
20
+ ## 1. Model Types (Omnify)
21
+
22
+ **Location**: `src/types/model/`
23
+
24
+ **Source**: Auto-generated from `.omnify/schemas/`
25
+
26
+ ```typescript
27
+ // ✅ Import from @/types/model
28
+ import type { User, UserCreate, UserUpdate } from "@/types/model";
29
+ import type { DateTimeString } from "@/types/model";
30
+ import { getUserRules } from "@/types/model";
31
+
32
+ // ❌ DON'T define model types manually
33
+ interface User { ... } // WRONG - already generated
34
+ ```
35
+
36
+ ### Structure
37
+
38
+ ```
39
+ src/types/model/
40
+ ├── common.ts ❌ DO NOT EDIT
41
+ │ # LocaleMap, ValidationRule, DateTimeString
42
+ ├── base/ ❌ DO NOT EDIT
43
+ │ └── User.ts # User + UserCreate + UserUpdate
44
+ ├── rules/ ❌ DO NOT EDIT
45
+ │ └── User.rules.ts # getUserRules(), getUserDisplayName()
46
+ ├── enum/ ❌ DO NOT EDIT (if exists)
47
+ │ └── PostStatus.ts
48
+ ├── index.ts ❌ DO NOT EDIT (re-exports)
49
+ └── User.ts ✅ CAN EDIT (extension)
50
+ ```
51
+
52
+ ### Generated Types Per Model
53
+
54
+ ```typescript
55
+ // Auto-generated in base/User.ts:
56
+ interface User {
57
+ id: number;
58
+ name: string;
59
+ email: string;
60
+ created_at?: DateTimeString; // Uses DateTimeString
61
+ updated_at?: DateTimeString;
62
+ }
63
+
64
+ type UserCreate = Omit<User, 'id' | 'created_at' | 'updated_at'>;
65
+ type UserUpdate = Partial<UserCreate>;
66
+ ```
67
+
68
+ ### Extending Model Types
69
+
70
+ ```typescript
71
+ // src/types/model/User.ts (safe to edit)
72
+ import type { User as UserBase } from "./base/User.js";
73
+
74
+ export interface User extends UserBase {
75
+ // Frontend-only computed properties
76
+ fullName?: string;
77
+
78
+ // UI state
79
+ isSelected?: boolean;
80
+ }
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 2. Using Generated Types
86
+
87
+ ### Create/Update Types
88
+
89
+ ```typescript
90
+ // ✅ Use Omnify-generated types
91
+ import type { User, UserCreate, UserUpdate } from "@/types/model";
92
+
93
+ const userService = {
94
+ create: (input: UserCreate) => api.post("/api/users", input),
95
+ update: (id: number, input: UserUpdate) => api.put(`/api/users/${id}`, input),
96
+ };
97
+ ```
98
+
99
+ ### Validation Rules with Ant Design Form.Item
100
+
101
+ ```typescript
102
+ import { userSchemas, getCustomerFieldLabel } from "@/types/model/User";
103
+ import { zodRule } from "@/lib/form-validation";
104
+ import { useLocale } from "next-intl";
105
+
106
+ function UserForm() {
107
+ const locale = useLocale();
108
+ const label = (key: string) => getCustomerFieldLabel(key, locale);
109
+
110
+ return (
111
+ <Form>
112
+ {/* Name */}
113
+ <Form.Item
114
+ name="name"
115
+ label={label("name")}
116
+ rules={[zodRule(userSchemas.name, label("name"))]}
117
+ >
118
+ <Input />
119
+ </Form.Item>
120
+
121
+ {/* Email */}
122
+ <Form.Item
123
+ name="email"
124
+ label={label("email")}
125
+ rules={[zodRule(userSchemas.email, label("email"))]}
126
+ >
127
+ <Input />
128
+ </Form.Item>
129
+ </Form>
130
+ );
131
+ }
132
+ ```
133
+
134
+ **Key Points:**
135
+ - Import `{model}Schemas` for Zod validation schemas
136
+ - Import `zodRule` from `@/lib/form-validation`
137
+ - Use `zodRule(schema, displayName)` in Form.Item rules
138
+ - Comment `{/* Field Name */}` before each Form.Item for clarity
139
+
140
+ ### DateTimeString
141
+
142
+ ```typescript
143
+ import type { DateTimeString } from "@/types/model";
144
+ import { formatDateTime } from "@/lib/dayjs";
145
+
146
+ interface Event {
147
+ scheduled_at: DateTimeString; // ISO 8601 UTC string
148
+ }
149
+
150
+ // Display
151
+ formatDateTime(event.scheduled_at); // "2024/01/15 19:30"
152
+ ```
153
+
154
+ ---
155
+
156
+ ## 3. API Params Types (Manual)
157
+
158
+ **Location**: Service file (colocated)
159
+
160
+ **Only define query params (not in Omnify):**
161
+
162
+ ```typescript
163
+ // services/users.ts
164
+ import type { User, UserCreate, UserUpdate } from "@/types/model";
165
+
166
+ /** Query params for listing users (GET /api/users) */
167
+ export interface UserListParams {
168
+ search?: string;
169
+ role?: string;
170
+ page?: number;
171
+ per_page?: number;
172
+ sort_by?: keyof User;
173
+ sort_order?: "asc" | "desc";
174
+ }
175
+
176
+ export const userService = {
177
+ list: (params?: UserListParams) => ...,
178
+ create: (input: UserCreate) => ..., // ← Use Omnify type
179
+ update: (id: number, input: UserUpdate) => ..., // ← Use Omnify type
180
+ };
181
+ ```
182
+
183
+ ---
184
+
185
+ ## 4. API Response Types
186
+
187
+ **Location**: `src/lib/api.ts`
188
+
189
+ **Naming**: `{Name}Response`, `Paginated{Name}`
190
+
191
+ ```typescript
192
+ // lib/api.ts
193
+
194
+ /** Laravel paginated response */
195
+ export interface PaginatedResponse<T> {
196
+ data: T[];
197
+ links: {
198
+ first: string | null;
199
+ last: string | null;
200
+ prev: string | null;
201
+ next: string | null;
202
+ };
203
+ meta: {
204
+ current_page: number;
205
+ from: number | null;
206
+ last_page: number;
207
+ per_page: number;
208
+ to: number | null;
209
+ total: number;
210
+ };
211
+ }
212
+
213
+ /** Laravel single resource response */
214
+ export interface ResourceResponse<T> {
215
+ data: T;
216
+ }
217
+
218
+ /** Laravel validation error (422) */
219
+ export interface ValidationError {
220
+ message: string;
221
+ errors: Record<string, string[]>;
222
+ }
223
+ ```
224
+
225
+ ### Usage in Service
226
+
227
+ ```typescript
228
+ import api, { PaginatedResponse } from "@/lib/api";
229
+ import type { User } from "@/types/model";
230
+
231
+ export const userService = {
232
+ list: async (params?: UserListParams): Promise<PaginatedResponse<User>> => {
233
+ const { data } = await api.get("/api/users", { params });
234
+ return data;
235
+ },
236
+ };
237
+ ```
238
+
239
+ ---
240
+
241
+ ## 4. Component Props Types
242
+
243
+ **Location**: Same file as component (inline)
244
+
245
+ **Naming**: `{Component}Props`
246
+
247
+ ```typescript
248
+ // components/tables/UserTable.tsx
249
+
250
+ import type { User } from "@/types/model";
251
+ import type { PaginatedResponse } from "@/lib/api";
252
+
253
+ // ─────────────────────────────────────────────────────────────────
254
+ // Props - Define at top of file
255
+ // ─────────────────────────────────────────────────────────────────
256
+
257
+ interface UserTableProps {
258
+ users: User[];
259
+ loading?: boolean;
260
+ pagination?: PaginatedResponse<User>["meta"];
261
+ onPageChange?: (page: number) => void;
262
+ onEdit?: (user: User) => void;
263
+ onDelete?: (user: User) => void;
264
+ }
265
+
266
+ // ─────────────────────────────────────────────────────────────────
267
+ // Component
268
+ // ─────────────────────────────────────────────────────────────────
269
+
270
+ export function UserTable({
271
+ users,
272
+ loading = false,
273
+ pagination,
274
+ onPageChange,
275
+ onEdit,
276
+ onDelete,
277
+ }: UserTableProps) {
278
+ return <Table ... />;
279
+ }
280
+ ```
281
+
282
+ ### When to Export Props
283
+
284
+ ```typescript
285
+ // ✅ Export if other components need it
286
+ export interface UserTableProps { ... }
287
+
288
+ // ✅ Don't export if only used internally
289
+ interface UserTableProps { ... }
290
+ ```
291
+
292
+ ---
293
+
294
+ ## 5. Hook Types
295
+
296
+ **Location**: Hook file (inline or inferred)
297
+
298
+ **Approach**: Let TypeScript infer return types when possible
299
+
300
+ ```typescript
301
+ // hooks/useUsers.ts
302
+
303
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
304
+ import { userService, UserCreateInput } from "@/services/users";
305
+ import { queryKeys } from "@/lib/queryKeys";
306
+
307
+ export function useUsers(params?: UserListParams) {
308
+ // Return type is inferred from userService.list
309
+ return useQuery({
310
+ queryKey: queryKeys.users.list(params),
311
+ queryFn: () => userService.list(params),
312
+ });
313
+ }
314
+
315
+ export function useCreateUser() {
316
+ const queryClient = useQueryClient();
317
+
318
+ // Return type is inferred from useMutation
319
+ return useMutation({
320
+ mutationFn: (input: UserCreateInput) => userService.create(input),
321
+ onSuccess: () => {
322
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
323
+ },
324
+ });
325
+ }
326
+ ```
327
+
328
+ ### When to Define Return Type
329
+
330
+ ```typescript
331
+ // ✅ Let TypeScript infer (simpler, less maintenance)
332
+ export function useUsers(params?: UserListParams) {
333
+ return useQuery({ ... });
334
+ }
335
+
336
+ // ✅ Define explicitly if complex or for documentation
337
+ export function useAuth(): {
338
+ user: User | undefined;
339
+ isLoading: boolean;
340
+ login: (input: LoginInput) => Promise<void>;
341
+ logout: () => Promise<void>;
342
+ } {
343
+ ...
344
+ }
345
+ ```
346
+
347
+ ---
348
+
349
+ ## 6. Shared/Utility Types
350
+
351
+ **Location**: `src/types/index.ts` (only if used across many files)
352
+
353
+ ```typescript
354
+ // types/index.ts
355
+
356
+ /** Common ID type */
357
+ export type ID = number;
358
+
359
+ /** Nullable type helper */
360
+ export type Nullable<T> = T | null;
361
+
362
+ /** Make specific keys optional */
363
+ export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
364
+
365
+ /** Extract array element type */
366
+ export type ArrayElement<T> = T extends (infer U)[] ? U : never;
367
+ ```
368
+
369
+ ### When to Use Shared Types
370
+
371
+ ```typescript
372
+ // ✅ Use shared types for truly common patterns
373
+ import type { ID, Nullable } from "@/types";
374
+
375
+ interface Post {
376
+ id: ID;
377
+ author_id: ID;
378
+ published_at: Nullable<string>;
379
+ }
380
+
381
+ // ❌ Don't over-abstract
382
+ // Bad: Creating shared types for every little thing
383
+ export type UserName = string; // Just use string
384
+ export type UserId = number; // Just use number
385
+ ```
386
+
387
+ ---
388
+
389
+ ## Type Definition Checklist
390
+
391
+ ### Before Creating a Type
392
+
393
+ 1. **Is it a Model?** → Use `@/types/model` (Omnify)
394
+ 2. **Is it API input?** → Define in service file
395
+ 3. **Is it API response?** → Use/extend types in `lib/api.ts`
396
+ 4. **Is it component props?** → Define in component file
397
+ 5. **Is it used in 3+ places?** → Consider `types/index.ts`
398
+
399
+ ### Type Naming Conventions
400
+
401
+ | Type | Pattern | Example |
402
+ | ------------ | -------------------- | ------------------- |
403
+ | Model | PascalCase | `User`, `Post` |
404
+ | Create Input | `{Model}CreateInput` | `UserCreateInput` |
405
+ | Update Input | `{Model}UpdateInput` | `UserUpdateInput` |
406
+ | List Params | `{Model}ListParams` | `UserListParams` |
407
+ | Props | `{Component}Props` | `UserTableProps` |
408
+ | Response | `{Name}Response` | `PaginatedResponse` |
409
+
410
+ ---
411
+
412
+ ## Complete Example
413
+
414
+ ```typescript
415
+ // ═══════════════════════════════════════════════════════════════════
416
+ // types/model/User.ts (Omnify extension)
417
+ // ═══════════════════════════════════════════════════════════════════
418
+ import type { User as UserBase } from "./base/User.js";
419
+
420
+ export interface User extends UserBase {
421
+ // Add frontend-only properties if needed
422
+ }
423
+
424
+ // ═══════════════════════════════════════════════════════════════════
425
+ // services/users.ts
426
+ // ═══════════════════════════════════════════════════════════════════
427
+ import api, { PaginatedResponse } from "@/lib/api";
428
+ import type { User } from "@/types/model";
429
+
430
+ export interface UserCreateInput {
431
+ name: string;
432
+ email: string;
433
+ password: string;
434
+ }
435
+
436
+ export interface UserUpdateInput {
437
+ name?: string;
438
+ email?: string;
439
+ }
440
+
441
+ export interface UserListParams {
442
+ search?: string;
443
+ page?: number;
444
+ }
445
+
446
+ export const userService = {
447
+ list: async (params?: UserListParams): Promise<PaginatedResponse<User>> => {
448
+ const { data } = await api.get("/api/users", { params });
449
+ return data;
450
+ },
451
+ get: async (id: number): Promise<User> => {
452
+ const { data } = await api.get(`/api/users/${id}`);
453
+ return data.data ?? data;
454
+ },
455
+ create: async (input: UserCreateInput): Promise<User> => {
456
+ const { data } = await api.post("/api/users", input);
457
+ return data.data ?? data;
458
+ },
459
+ update: async (id: number, input: UserUpdateInput): Promise<User> => {
460
+ const { data } = await api.put(`/api/users/${id}`, input);
461
+ return data.data ?? data;
462
+ },
463
+ delete: async (id: number): Promise<void> => {
464
+ await api.delete(`/api/users/${id}`);
465
+ },
466
+ };
467
+
468
+ // ═══════════════════════════════════════════════════════════════════
469
+ // components/tables/UserTable.tsx
470
+ // ═══════════════════════════════════════════════════════════════════
471
+ import type { User } from "@/types/model";
472
+
473
+ interface UserTableProps {
474
+ users: User[];
475
+ loading?: boolean;
476
+ onEdit?: (user: User) => void;
477
+ }
478
+
479
+ export function UserTable({ users, loading, onEdit }: UserTableProps) {
480
+ return <Table dataSource={users} loading={loading} ... />;
481
+ }
482
+
483
+ // ═══════════════════════════════════════════════════════════════════
484
+ // app/(dashboard)/users/page.tsx
485
+ // ═══════════════════════════════════════════════════════════════════
486
+ "use client";
487
+
488
+ import { useQuery } from "@tanstack/react-query";
489
+ import { userService, UserListParams } from "@/services/users";
490
+ import { UserTable } from "@/components/tables/UserTable";
491
+ import { queryKeys } from "@/lib/queryKeys";
492
+
493
+ export default function UsersPage() {
494
+ const [params, setParams] = useState<UserListParams>({ page: 1 });
495
+
496
+ const { data, isLoading } = useQuery({
497
+ queryKey: queryKeys.users.list(params),
498
+ queryFn: () => userService.list(params),
499
+ });
500
+
501
+ return (
502
+ <UserTable
503
+ users={data?.data ?? []}
504
+ loading={isLoading}
505
+ onEdit={(user) => router.push(`/users/${user.id}/edit`)}
506
+ />
507
+ );
508
+ }
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Summary
514
+
515
+ | Type | Location | Why |
516
+ | -------- | -------------------- | ------------------------- |
517
+ | Model | `@/types/model` | Synced with DB via Omnify |
518
+ | Input | Service file | Colocated with API logic |
519
+ | Response | `lib/api.ts` | Shared Laravel patterns |
520
+ | Props | Component file | Colocated with component |
521
+ | Hook | Hook file (inferred) | TypeScript handles it |
522
+ | Utility | `types/index.ts` | Only if widely used |
523
+
524
+ **Philosophy**: Keep types close to their usage. Don't over-organize.
@@ -0,0 +1,13 @@
1
+ import { JapaneseNameField } from './JapaneseNameField.js';
2
+ import { JapaneseAddressField } from './JapaneseAddressField.js';
3
+ import { JapaneseBankField } from './JapaneseBankField.js';
4
+
5
+ // Namespace style exports (recommended)
6
+ export const OmnifyForm = {
7
+ JapaneseName: JapaneseNameField,
8
+ JapaneseAddress: JapaneseAddressField,
9
+ JapaneseBank: JapaneseBankField,
10
+ } as const;
11
+
12
+ // Legacy exports (backward compatible)
13
+ export { JapaneseNameField, JapaneseAddressField, JapaneseBankField };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Form validation utilities for Ant Design + Zod
3
+ * Generated by Omnify. You can customize this file.
4
+ */
5
+
6
+ import type { RuleObject } from 'antd/es/form';
7
+ import type { z } from 'zod';
8
+ import { getZodMessage } from './zod-i18n.js';
9
+
10
+ /**
11
+ * Convert Zod schema to Ant Design Form rule with i18n support
12
+ *
13
+ * @example
14
+ * // Set locale once at component level
15
+ * setZodLocale('ja');
16
+ *
17
+ * // Use without passing locale
18
+ * <Form.Item
19
+ * name="email"
20
+ * rules={[zodRule(customerSchemas.email, 'メールアドレス')]}
21
+ * >
22
+ * <Input />
23
+ * </Form.Item>
24
+ */
25
+ export function zodRule<T extends z.ZodTypeAny>(
26
+ schema: T,
27
+ displayName?: string
28
+ ): RuleObject {
29
+ const field = displayName ?? 'この項目';
30
+
31
+ return {
32
+ validator: async (_, value) => {
33
+ // Empty check - treat as required
34
+ if (value === undefined || value === null || value === '') {
35
+ if (schema.safeParse(undefined).success) return;
36
+ throw new Error(getZodMessage('required', { displayName: field }));
37
+ }
38
+
39
+ const result = schema.safeParse(value);
40
+ if (result.success) return;
41
+
42
+ // Get first Zod error
43
+ const issue = result.error.issues[0];
44
+ if (!issue) {
45
+ throw new Error(getZodMessage('required', { displayName: field }));
46
+ }
47
+
48
+ // Translate based on error type
49
+ switch (issue.code) {
50
+ case 'too_small':
51
+ if (issue.type === 'string' && issue.minimum === 1) {
52
+ throw new Error(getZodMessage('required', { displayName: field }));
53
+ }
54
+ if (issue.type === 'string') {
55
+ throw new Error(getZodMessage('minLength', { displayName: field, min: issue.minimum as number }));
56
+ }
57
+ throw new Error(getZodMessage('min', { displayName: field, min: issue.minimum as number }));
58
+
59
+ case 'too_big':
60
+ if (issue.type === 'string') {
61
+ throw new Error(getZodMessage('maxLength', { displayName: field, max: issue.maximum as number }));
62
+ }
63
+ throw new Error(getZodMessage('max', { displayName: field, max: issue.maximum as number }));
64
+
65
+ case 'invalid_string':
66
+ if (issue.validation === 'email') {
67
+ throw new Error(getZodMessage('email', { displayName: field }));
68
+ }
69
+ if (issue.validation === 'url') {
70
+ throw new Error(getZodMessage('url', { displayName: field }));
71
+ }
72
+ if (issue.validation === 'regex') {
73
+ throw new Error(getZodMessage('pattern', { displayName: field }));
74
+ }
75
+ break;
76
+
77
+ case 'invalid_type':
78
+ if (issue.received === 'undefined' || issue.received === 'null') {
79
+ throw new Error(getZodMessage('required', { displayName: field }));
80
+ }
81
+ break;
82
+ }
83
+
84
+ // Fallback to Zod's original message
85
+ throw new Error(issue.message);
86
+ },
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Create required rule with i18n message
92
+ *
93
+ * @example
94
+ * <Form.Item
95
+ * name="name"
96
+ * rules={[requiredRule('名前')]}
97
+ * >
98
+ * <Input />
99
+ * </Form.Item>
100
+ */
101
+ export function requiredRule(displayName: string): RuleObject {
102
+ return {
103
+ required: true,
104
+ message: getZodMessage('required', { displayName }),
105
+ };
106
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Japanese validation rules
3
+ *
4
+ * Usage with Zod:
5
+ * ```typescript
6
+ * import { z } from 'zod';
7
+ * import { kanaString, KATAKANA_PATTERN } from '@/omnify/lib/rules';
8
+ *
9
+ * // Method 1: Use kanaString helper
10
+ * const schema = z.object({
11
+ * name_kana: kanaString(), // 全角カタカナ (default)
12
+ * });
13
+ *
14
+ * // Method 2: Use pattern directly
15
+ * const schema2 = z.object({
16
+ * name_kana: z.string().regex(KATAKANA_PATTERN, '全角カタカナで入力してください'),
17
+ * });
18
+ * ```
19
+ */
20
+
21
+ export {
22
+ // Kana validation
23
+ kanaRules,
24
+ createKanaRegex,
25
+ validateKana,
26
+ getKanaPattern,
27
+ getKanaErrorMessage,
28
+ // Zod helpers
29
+ kanaString,
30
+ withKana,
31
+ // Pattern constants
32
+ KATAKANA_PATTERN,
33
+ KATAKANA_HALF_PATTERN,
34
+ HIRAGANA_PATTERN,
35
+ KANA_ANY_PATTERN,
36
+ // Presets
37
+ KATAKANA_FULL_WIDTH,
38
+ KATAKANA_HALF_WIDTH,
39
+ HIRAGANA,
40
+ KANA_ANY,
41
+ KATAKANA_WITH_NUMBERS,
42
+ // Types
43
+ type KanaRuleOptions,
44
+ } from './kana';
45
+
46
+ // Re-export as convenient aliases
47
+ export { getKanaPattern as kanaPattern } from './kana';
48
+ export { createKanaRegex as kanaRegex } from './kana';