@ram_28/kf-ai-sdk 1.0.19 → 1.0.20

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 (64) hide show
  1. package/README.md +45 -12
  2. package/dist/components/hooks/useFilter/types.d.ts +14 -11
  3. package/dist/components/hooks/useFilter/types.d.ts.map +1 -1
  4. package/dist/components/hooks/useFilter/useFilter.d.ts +1 -1
  5. package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
  6. package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -1
  7. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
  8. package/dist/components/hooks/useKanban/types.d.ts +5 -22
  9. package/dist/components/hooks/useKanban/types.d.ts.map +1 -1
  10. package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -1
  11. package/dist/components/hooks/useTable/types.d.ts +19 -31
  12. package/dist/components/hooks/useTable/types.d.ts.map +1 -1
  13. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
  14. package/dist/error-handling-CAoD0Kwb.cjs +1 -0
  15. package/dist/error-handling-CrhTtD88.js +14 -0
  16. package/dist/filter.cjs +1 -1
  17. package/dist/filter.mjs +1 -1
  18. package/dist/form.cjs +1 -1
  19. package/dist/form.mjs +338 -327
  20. package/dist/index.d.ts +18 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/kanban.cjs +2 -2
  23. package/dist/kanban.mjs +332 -322
  24. package/dist/table.cjs +1 -1
  25. package/dist/table.mjs +113 -96
  26. package/dist/table.types.d.ts +1 -1
  27. package/dist/table.types.d.ts.map +1 -1
  28. package/dist/types/common.d.ts +26 -6
  29. package/dist/types/common.d.ts.map +1 -1
  30. package/dist/useFilter-DzpP_ag0.cjs +1 -0
  31. package/dist/useFilter-H5bgAZQF.js +120 -0
  32. package/dist/utils/api/buildListOptions.d.ts +43 -0
  33. package/dist/utils/api/buildListOptions.d.ts.map +1 -0
  34. package/dist/utils/api/index.d.ts +2 -0
  35. package/dist/utils/api/index.d.ts.map +1 -0
  36. package/dist/utils/error-handling.d.ts +41 -0
  37. package/dist/utils/error-handling.d.ts.map +1 -0
  38. package/dist/utils/index.d.ts +2 -0
  39. package/dist/utils/index.d.ts.map +1 -1
  40. package/docs/QUICK_REFERENCE.md +142 -420
  41. package/docs/useAuth.md +52 -340
  42. package/docs/useFilter.md +858 -162
  43. package/docs/useForm.md +712 -501
  44. package/docs/useKanban.md +534 -279
  45. package/docs/useTable.md +725 -214
  46. package/package.json +1 -1
  47. package/sdk/components/hooks/useFilter/types.ts +14 -11
  48. package/sdk/components/hooks/useFilter/useFilter.ts +20 -18
  49. package/sdk/components/hooks/useForm/apiClient.ts +2 -1
  50. package/sdk/components/hooks/useForm/useForm.ts +35 -11
  51. package/sdk/components/hooks/useKanban/types.ts +7 -23
  52. package/sdk/components/hooks/useKanban/useKanban.ts +54 -18
  53. package/sdk/components/hooks/useTable/types.ts +26 -32
  54. package/sdk/components/hooks/useTable/useTable.llm.txt +8 -22
  55. package/sdk/components/hooks/useTable/useTable.ts +70 -25
  56. package/sdk/index.ts +154 -10
  57. package/sdk/table.types.ts +3 -0
  58. package/sdk/types/common.ts +31 -6
  59. package/sdk/utils/api/buildListOptions.ts +120 -0
  60. package/sdk/utils/api/index.ts +2 -0
  61. package/sdk/utils/error-handling.ts +150 -0
  62. package/sdk/utils/index.ts +6 -0
  63. package/dist/useFilter-Dofowpr_.cjs +0 -1
  64. package/dist/useFilter-Dv-mr9QW.js +0 -117
package/docs/useForm.md CHANGED
@@ -1,207 +1,48 @@
1
1
  # useForm
2
2
 
3
- ## Brief Description
3
+ Form state management with automatic schema validation, computed fields, and react-hook-form integration.
4
4
 
5
- - Integrates react-hook-form with backend Business Data Object (BDO) schemas for automatic validation and computed field handling
6
- - Fetches schema metadata from the API and generates validation rules, default values, and field permissions automatically
7
- - Supports both create and update operations with automatic draft API calls for server-side computed fields
8
- - Provides flattened state accessors (`errors`, `isValid`, `isDirty`) alongside react-hook-form's standard methods
9
-
10
- ## Type Reference
5
+ ## Imports
11
6
 
12
7
  ```typescript
13
- import { useForm } from "@ram_28/kf-ai-sdk/form";
14
- import type {
15
- // Core types
16
- UseFormOptionsType,
17
- UseFormReturnType,
18
- FormOperationType,
19
- FormModeType,
20
-
21
- // Form field configuration
22
- FormFieldConfigType,
23
- FormSchemaConfigType,
24
- FormFieldTypeType,
25
- SelectOptionType,
26
- FieldPermissionType,
27
-
28
- // Result types
29
- FieldValidationResultType,
30
- SubmissionResultType,
31
-
32
- // BDO Schema types (advanced)
33
- BDOSchemaType,
34
- BDOFieldDefinitionType,
35
- SchemaValidationRuleType,
36
- } from "@ram_28/kf-ai-sdk/form/types";
37
-
38
- // Error utilities
39
8
  import {
9
+ useForm,
40
10
  parseApiError,
41
11
  isNetworkError,
42
12
  isValidationError,
43
13
  clearFormCache,
44
14
  } from "@ram_28/kf-ai-sdk/form";
45
-
46
- // Re-exported from react-hook-form (available from SDK)
47
15
  import type {
48
- UseFormRegister,
49
16
  FieldErrors,
50
- FormState,
51
- Path,
52
- PathValue,
53
- } from "@ram_28/kf-ai-sdk/form.types";
54
-
55
- // FieldErrors type from react-hook-form
56
- // Maps field names to their error objects (message, type, etc.)
57
- // Used in handleSubmit's onError callback for validation errors
58
- type FieldErrors<T> = {
59
- [K in keyof T]?: {
60
- type: string;
61
- message?: string;
62
- ref?: React.RefObject<any>;
63
- };
64
- } & {
65
- root?: Record<string, { type: string; message?: string }>; // Cross-field errors
66
- };
67
-
68
- // FormState type from react-hook-form
69
- // Contains all form state information
70
- interface FormState<T> {
71
- isDirty: boolean; // Form has been modified
72
- isValid: boolean; // All validations pass
73
- isSubmitting: boolean; // Form is being submitted
74
- isSubmitted: boolean; // Form has been submitted at least once
75
- isSubmitSuccessful: boolean; // Last submission was successful
76
- isLoading: boolean; // Form is loading async default values
77
- isValidating: boolean; // Form is validating
78
- submitCount: number; // Number of times form was submitted
79
- errors: FieldErrors<T>; // Current validation errors
80
- touchedFields: Partial<Record<keyof T, boolean>>; // Fields that have been touched
81
- dirtyFields: Partial<Record<keyof T, boolean>>; // Fields that have been modified
82
- }
83
-
84
- // Form operation type
85
- type FormOperationType = "create" | "update";
86
-
87
- // Form field input types
88
- type FormFieldTypeType =
89
- | "text"
90
- | "number"
91
- | "email"
92
- | "password"
93
- | "date"
94
- | "datetime-local"
95
- | "checkbox"
96
- | "select"
97
- | "textarea"
98
- | "reference";
99
-
100
- // Select option for dropdown fields
101
- interface SelectOptionType {
102
- value: any;
103
- label: string;
104
- }
17
+ UseFormOptionsType,
18
+ UseFormReturnType,
19
+ } from "@ram_28/kf-ai-sdk/form/types";
20
+ ```
105
21
 
106
- // Field permission for current user
107
- interface FieldPermissionType {
108
- editable: boolean;
109
- readable: boolean;
110
- hidden: boolean;
111
- }
22
+ ## Product Class Pattern
112
23
 
113
- // Field rule IDs by category
114
- interface FieldRuleIdsType {
115
- validation: string[];
116
- computation: string[];
117
- businessLogic: string[];
118
- }
24
+ The recommended pattern uses a role-based BDO wrapper class (like `Product`) instead of hardcoded source strings:
119
25
 
120
- // Form field configuration for rendering
121
- interface FormFieldConfigType {
122
- name: string;
123
- type: FormFieldTypeType;
124
- label: string;
125
- required: boolean;
126
- computed: boolean;
127
- defaultValue?: any;
128
- options?: SelectOptionType[];
129
- validation: any;
130
- description?: string;
131
- _bdoField: BDOFieldDefinitionType;
132
- permission: FieldPermissionType;
133
- rules: FieldRuleIdsType;
134
- }
26
+ ```tsx
27
+ // sources/Product.ts - Role-based BDO wrapper
28
+ import { Role, Roles } from "./roles";
135
29
 
136
- // Form schema configuration after processing
137
- interface FormSchemaConfigType {
138
- fields: Record<string, FormFieldConfigType>;
139
- fieldOrder: string[];
140
- computedFields: string[];
141
- requiredFields: string[];
142
- crossFieldValidation: SchemaValidationRuleType[];
143
- rules: {
144
- validation: Record<string, SchemaValidationRuleType>;
145
- computation: Record<string, SchemaValidationRuleType>;
146
- businessLogic: Record<string, SchemaValidationRuleType>;
147
- };
148
- fieldRules: Record<string, FieldRuleIdsType>;
149
- rolePermissions?: Record<string, RolePermissionType>;
150
- }
30
+ export type ProductType<TRole extends Role> = /* role-mapped types */;
151
31
 
152
- // BDO field definition structure
153
- interface BDOFieldDefinitionType {
154
- Id: string;
155
- Name: string;
156
- Type:
157
- | "String"
158
- | "Number"
159
- | "Boolean"
160
- | "Date"
161
- | "DateTime"
162
- | "Reference"
163
- | "Array"
164
- | "Object";
165
- Required?: boolean;
166
- Unique?: boolean;
167
- DefaultValue?: DefaultValueExpressionType;
168
- Formula?: ComputedFieldFormulaType;
169
- Computed?: boolean;
170
- Validation?: string[] | SchemaValidationRuleType[];
171
- Values?: FieldOptionsConfigType;
172
- Description?: string;
32
+ export class Product<TRole extends Role = typeof Roles.Admin> {
33
+ constructor(_role: TRole) {}
34
+ get _id(): string { return "BDO_AmazonProductMaster"; }
35
+ // API methods: list, get, create, update, delete, draft, etc.
173
36
  }
37
+ ```
174
38
 
175
- // BDO schema structure
176
- interface BDOSchemaType {
177
- Id: string;
178
- Name: string;
179
- Kind: "BusinessObject";
180
- Description: string;
181
- Rules: {
182
- Computation?: Record<string, SchemaValidationRuleType>;
183
- Validation?: Record<string, SchemaValidationRuleType>;
184
- BusinessLogic?: Record<string, SchemaValidationRuleType>;
185
- };
186
- Fields: Record<string, BDOFieldDefinitionType>;
187
- RolePermission: Record<string, RolePermissionType>;
188
- Roles: Record<string, { Name: string; Description: string }>;
189
- }
39
+ This pattern provides type-safe role-based field access, consistent with `useTable` and other hooks.
190
40
 
191
- // Field validation result
192
- interface FieldValidationResultType<T = Record<string, any>> {
193
- isValid: boolean;
194
- message?: string;
195
- fieldName?: keyof T;
196
- }
41
+ ## Type Definitions
197
42
 
198
- // Form submission result
199
- interface SubmissionResultType {
200
- success: boolean;
201
- data?: any;
202
- error?: Error;
203
- recordId?: string;
204
- }
43
+ ```typescript
44
+ // Form operation type
45
+ type FormOperationType = "create" | "update";
205
46
 
206
47
  // Hook options
207
48
  interface UseFormOptionsType<T> {
@@ -212,61 +53,30 @@ interface UseFormOptionsType<T> {
212
53
  mode?: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all";
213
54
  enabled?: boolean;
214
55
  userRole?: string;
215
- schema?: BDOSchemaType;
216
- onSchemaError?: (error: Error) => void; // Schema loading errors only
56
+ onSchemaError?: (error: Error) => void;
217
57
  }
218
58
 
219
59
  // Hook return type
220
60
  interface UseFormReturnType<T> {
221
61
  // React Hook Form methods
222
62
  register: UseFormRegister<T>;
223
- /**
224
- * handleSubmit follows RHF pattern with optional callbacks:
225
- * - onSuccess: Called with API response data on successful submission
226
- * - onError: Called with FieldErrors (validation) or Error (API failure)
227
- *
228
- * Usage:
229
- * form.handleSubmit() // No callbacks
230
- * form.handleSubmit(onSuccess) // Success only
231
- * form.handleSubmit(onSuccess, onError) // Both callbacks
232
- * form.handleSubmit(undefined, onError) // Error only
233
- */
234
- handleSubmit: (
235
- onSuccess?: (data: T, e?: React.FormEvent) => void | Promise<void>,
236
- onError?: (
237
- error: FieldErrors<T> | Error,
238
- e?: React.FormEvent,
239
- ) => void | Promise<void>,
240
- ) => (e?: React.FormEvent) => Promise<void>;
241
- watch: <K extends Path<T>>(
242
- name?: K,
243
- ) => K extends Path<T> ? PathValue<T, K> : T;
244
- setValue: <K extends Path<T>>(name: K, value: PathValue<T, K>) => void;
245
- reset: (values?: T) => void;
246
-
247
- // Flattened state (recommended)
63
+ handleSubmit: (onSuccess?, onError?) => (e?) => Promise<void>;
64
+ watch: (name?) => any;
65
+ setValue: (name, value) => void;
66
+ reset: (values?) => void;
67
+
68
+ // Form state
248
69
  errors: FieldErrors<T>;
249
70
  isValid: boolean;
250
71
  isDirty: boolean;
251
72
  isSubmitting: boolean;
252
73
  isSubmitSuccessful: boolean;
253
74
 
254
- // Legacy (backward compatible)
255
- formState: FormState<T>;
256
-
257
75
  // Loading states
258
76
  isLoadingInitialData: boolean;
259
77
  isLoadingRecord: boolean;
260
78
  isLoading: boolean;
261
79
 
262
- // Draft state
263
- draftId: string | null;
264
- isCreatingDraft: boolean;
265
-
266
- // Error handling
267
- loadError: Error | null;
268
- hasError: boolean;
269
-
270
80
  // Schema info
271
81
  schema: BDOSchemaType | null;
272
82
  schemaConfig: FormSchemaConfigType | null;
@@ -274,338 +84,605 @@ interface UseFormReturnType<T> {
274
84
  requiredFields: Array<keyof T>;
275
85
 
276
86
  // Field helpers
277
- getField: <K extends keyof T>(fieldName: K) => FormFieldConfigType | null;
87
+ getField: (fieldName) => FormFieldConfigType | null;
278
88
  getFields: () => Record<keyof T, FormFieldConfigType>;
279
- hasField: <K extends keyof T>(fieldName: K) => boolean;
280
- isFieldRequired: <K extends keyof T>(fieldName: K) => boolean;
281
- isFieldComputed: <K extends keyof T>(fieldName: K) => boolean;
282
-
283
- // Operations
284
- refreshSchema: () => Promise<void>;
285
- validateForm: () => Promise<boolean>;
286
- clearErrors: () => void;
89
+ isFieldRequired: (fieldName) => boolean;
90
+ isFieldComputed: (fieldName) => boolean;
91
+ }
92
+
93
+ // Field configuration
94
+ interface FormFieldConfigType {
95
+ name: string;
96
+ type:
97
+ | "text"
98
+ | "number"
99
+ | "email"
100
+ | "date"
101
+ | "checkbox"
102
+ | "select"
103
+ | "textarea";
104
+ label: string;
105
+ required: boolean;
106
+ computed: boolean;
107
+ defaultValue?: any;
108
+ options?: { value: any; label: string }[];
109
+ permission: { editable: boolean; readable: boolean; hidden: boolean };
287
110
  }
288
111
  ```
289
112
 
290
- ## Usage Example
113
+ ## Basic Example
114
+
115
+ A minimal create form with field registration and submission.
291
116
 
292
117
  ```tsx
293
- import { useState } from "react";
294
- import { useTable } from "@ram_28/kf-ai-sdk/table";
295
118
  import { useForm } from "@ram_28/kf-ai-sdk/form";
296
119
  import type {
297
120
  UseFormOptionsType,
298
121
  UseFormReturnType,
299
- FormOperationType,
300
- FormFieldConfigType,
301
- FormSchemaConfigType,
302
- BDOFieldDefinitionType,
303
- BDOSchemaType,
304
122
  } from "@ram_28/kf-ai-sdk/form/types";
305
- import type {
306
- UseTableOptionsType,
307
- UseTableReturnType,
308
- ColumnDefinitionType,
309
- } from "@ram_28/kf-ai-sdk/table/types";
310
- import type { FieldErrors } from "@ram_28/kf-ai-sdk/form.types";
311
123
  import { Product, ProductType } from "../sources";
312
124
  import { Roles } from "../sources/roles";
313
125
 
314
- // Get the typed product for the Seller role (who can create/update products)
315
126
  type SellerProduct = ProductType<typeof Roles.Seller>;
316
127
 
317
- function ProductManagementPage() {
318
- // Instantiate the Product source with role
128
+ function CreateProductForm() {
319
129
  const product = new Product(Roles.Seller);
320
130
 
321
- const [showForm, setShowForm] = useState(false);
322
- const [selectedProduct, setSelectedProduct] = useState<SellerProduct | null>(
323
- null,
324
- );
325
- const [formMode, setFormMode] = useState<FormOperationType>("create");
326
-
327
- // Table for listing products
328
- const table = useTable<SellerProduct>({
329
- source: product._id,
330
- columns: [
331
- { fieldId: "Title", label: "Name" },
332
- { fieldId: "Price", label: "Price" },
333
- { fieldId: "Discount", label: "Discount %" },
334
- ],
335
- enablePagination: true,
336
- });
337
-
338
- // Form configuration with all options
339
- const formOptions: UseFormOptionsType<SellerProduct> = {
131
+ const options: UseFormOptionsType<SellerProduct> = {
340
132
  source: product._id,
341
- operation: formMode,
342
- recordId: selectedProduct?._id,
343
- enabled: showForm,
344
- mode: "onBlur",
133
+ operation: "create",
345
134
  defaultValues: {
346
135
  Title: "",
347
- Description: "",
348
136
  Price: 0,
349
- MRP: 0,
350
- Category: "Electronics",
351
- Stock: 0,
137
+ Category: "",
352
138
  },
353
- onSchemaError: (error: Error) =>
354
- console.error("Schema error:", error.message),
355
139
  };
356
140
 
357
- const form: UseFormReturnType<SellerProduct> =
358
- useForm<SellerProduct>(formOptions);
141
+ const form: UseFormReturnType<SellerProduct> = useForm<SellerProduct>(options);
359
142
 
360
- // Handle form submission with new RHF-style callbacks
361
- const onFormSuccess = (data: SellerProduct) => {
362
- console.log("Product saved:", data);
363
- setShowForm(false);
364
- table.refetch();
143
+ const onSuccess = (data: SellerProduct) => {
144
+ console.log("Product created:", data);
365
145
  };
366
146
 
367
- const onFormError = (error: FieldErrors<SellerProduct> | Error) => {
368
- if (error instanceof Error) {
369
- // API error
370
- console.error("Submit error:", error.message);
371
- } else {
372
- // Validation errors (FieldErrors)
373
- console.error("Validation errors:", error);
374
- }
147
+ const onError = (error: Error) => {
148
+ console.error("Failed to create:", error.message);
375
149
  };
376
150
 
377
- // Access processed schema
378
- const displaySchemaInfo = () => {
379
- const schema: FormSchemaConfigType | null = form.schemaConfig;
380
- if (!schema) return null;
151
+ if (form.isLoading) return <div>Loading form...</div>;
381
152
 
382
- return (
383
- <div className="schema-info">
384
- <p>Required: {schema.requiredFields.join(", ")}</p>
385
- <p>Computed: {schema.computedFields.join(", ")}</p>
386
- <p>Field Order: {schema.fieldOrder.join(", ")}</p>
153
+ return (
154
+ <form onSubmit={form.handleSubmit(onSuccess, onError)}>
155
+ <div>
156
+ <label>Title</label>
157
+ <input {...form.register("Title")} />
158
+ {form.errors.Title && <span>{form.errors.Title.message}</span>}
387
159
  </div>
388
- );
389
- };
390
160
 
391
- // Render a field using FormFieldConfigType metadata
392
- const renderField = (fieldName: keyof SellerProduct) => {
393
- const field: FormFieldConfigType | null = form.getField(fieldName);
394
- if (!field || field.permission.hidden) return null;
161
+ <div>
162
+ <label>Price</label>
163
+ <input type="number" {...form.register("Price")} />
164
+ {form.errors.Price && <span>{form.errors.Price.message}</span>}
165
+ </div>
395
166
 
396
- const isDisabled = !field.permission.editable || field.computed;
397
- const error = form.errors[fieldName];
167
+ <div>
168
+ <label>Category</label>
169
+ <input {...form.register("Category")} />
170
+ </div>
398
171
 
399
- return (
400
- <div key={field.name} className="form-field">
401
- <label htmlFor={field.name}>
402
- {field.label}
403
- {field.required && <span className="required">*</span>}
404
- {field.computed && <span className="computed">(auto)</span>}
405
- </label>
406
-
407
- {field.type === "select" && field.options ? (
408
- <select
409
- id={field.name}
410
- disabled={isDisabled}
411
- {...form.register(fieldName as any)}
412
- >
413
- <option value="">Select {field.label}</option>
414
- {field.options.map((opt) => (
415
- <option key={opt.value} value={opt.value}>
416
- {opt.label}
417
- </option>
418
- ))}
419
- </select>
420
- ) : field.computed ? (
172
+ <button type="submit" disabled={form.isSubmitting}>
173
+ {form.isSubmitting ? "Creating..." : "Create Product"}
174
+ </button>
175
+ </form>
176
+ );
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Create vs Update
183
+
184
+ ### Create Form
185
+
186
+ Initialize a form for creating new records.
187
+
188
+ ```tsx
189
+ import { Product, ProductType } from "../sources";
190
+ import { Roles } from "../sources/roles";
191
+
192
+ type SellerProduct = ProductType<typeof Roles.Seller>;
193
+
194
+ function CreateForm() {
195
+ const product = new Product(Roles.Seller);
196
+
197
+ const form = useForm<SellerProduct>({
198
+ source: product._id,
199
+ operation: "create",
200
+ defaultValues: {
201
+ Title: "",
202
+ Price: 0,
203
+ Stock: 0,
204
+ },
205
+ });
206
+
207
+ return (
208
+ <form onSubmit={form.handleSubmit()}>
209
+ <input {...form.register("Title")} placeholder="Product title" />
210
+ <input type="number" {...form.register("Price")} placeholder="Price" />
211
+ <button type="submit">Create</button>
212
+ </form>
213
+ );
214
+ }
215
+ ```
216
+
217
+ ### Edit Form
218
+
219
+ Load existing record data for editing.
220
+
221
+ ```tsx
222
+ function EditForm({ productId }: { productId: string }) {
223
+ const product = new Product(Roles.Seller);
224
+
225
+ const form = useForm<SellerProduct>({
226
+ source: product._id,
227
+ operation: "update",
228
+ recordId: productId,
229
+ });
230
+
231
+ if (form.isLoadingRecord) return <div>Loading product...</div>;
232
+
233
+ return (
234
+ <form onSubmit={form.handleSubmit()}>
235
+ <input {...form.register("Title")} />
236
+ <input type="number" {...form.register("Price")} />
237
+ <button type="submit" disabled={!form.isDirty}>
238
+ Save Changes
239
+ </button>
240
+ </form>
241
+ );
242
+ }
243
+ ```
244
+
245
+ ### Toggle Between Create and Edit
246
+
247
+ Switch form mode based on selection.
248
+
249
+ ```tsx
250
+ function ProductForm({ existingProduct }: { existingProduct?: SellerProduct }) {
251
+ const product = new Product(Roles.Seller);
252
+ const isEditing = !!existingProduct;
253
+
254
+ const form = useForm<SellerProduct>({
255
+ source: product._id,
256
+ operation: isEditing ? "update" : "create",
257
+ recordId: existingProduct?._id,
258
+ defaultValues: existingProduct ?? { Title: "", Price: 0 },
259
+ });
260
+
261
+ return (
262
+ <form onSubmit={form.handleSubmit()}>
263
+ <h2>{isEditing ? "Edit Product" : "New Product"}</h2>
264
+ <input {...form.register("Title")} />
265
+ <input type="number" {...form.register("Price")} />
266
+ <button type="submit">{isEditing ? "Update" : "Create"}</button>
267
+ </form>
268
+ );
269
+ }
270
+ ```
271
+
272
+ ---
273
+
274
+ ## Field Rendering
275
+
276
+ ### Using getField for Metadata
277
+
278
+ Access field configuration from the schema.
279
+
280
+ ```tsx
281
+ function DynamicField({
282
+ form,
283
+ fieldName,
284
+ }: {
285
+ form: UseFormReturnType<SellerProduct>;
286
+ fieldName: keyof SellerProduct;
287
+ }) {
288
+ const field = form.getField(fieldName);
289
+
290
+ if (!field || field.permission.hidden) return null;
291
+
292
+ const isDisabled = !field.permission.editable || field.computed;
293
+
294
+ return (
295
+ <div className="field">
296
+ <label>
297
+ {field.label}
298
+ {field.required && <span className="required">*</span>}
299
+ {field.computed && <span className="computed">(auto)</span>}
300
+ </label>
301
+
302
+ {field.type === "select" && field.options ? (
303
+ <select {...form.register(fieldName)} disabled={isDisabled}>
304
+ <option value="">Select {field.label}</option>
305
+ {field.options.map((opt) => (
306
+ <option key={opt.value} value={opt.value}>
307
+ {opt.label}
308
+ </option>
309
+ ))}
310
+ </select>
311
+ ) : (
312
+ <input
313
+ type={field.type === "number" ? "number" : "text"}
314
+ {...form.register(fieldName)}
315
+ disabled={isDisabled}
316
+ />
317
+ )}
318
+
319
+ {form.errors[fieldName] && (
320
+ <span className="error">{form.errors[fieldName]?.message}</span>
321
+ )}
322
+ </div>
323
+ );
324
+ }
325
+ ```
326
+
327
+ ### Handling Computed Fields
328
+
329
+ Display auto-calculated fields as read-only.
330
+
331
+ ```tsx
332
+ function FormWithComputedFields() {
333
+ const product = new Product(Roles.Seller);
334
+
335
+ const form = useForm<SellerProduct>({
336
+ source: product._id,
337
+ operation: "create",
338
+ });
339
+
340
+ return (
341
+ <form onSubmit={form.handleSubmit()}>
342
+ <div>
343
+ <label>MRP</label>
344
+ <input type="number" {...form.register("MRP")} />
345
+ </div>
346
+
347
+ <div>
348
+ <label>Price</label>
349
+ <input type="number" {...form.register("Price")} />
350
+ </div>
351
+
352
+ {/* Discount is computed from MRP and Price */}
353
+ {form.isFieldComputed("Discount") && (
354
+ <div>
355
+ <label>Discount % (auto-calculated)</label>
421
356
  <input
422
- id={field.name}
423
- type={field.type === "number" ? "number" : "text"}
424
- value={form.watch(fieldName as any) ?? field.defaultValue ?? ""}
357
+ type="number"
358
+ value={form.watch("Discount") ?? 0}
425
359
  readOnly
426
360
  disabled
427
361
  />
428
- ) : (
429
- <input
430
- id={field.name}
431
- type={field.type === "number" ? "number" : "text"}
432
- disabled={isDisabled}
433
- {...form.register(fieldName as any)}
434
- />
362
+ </div>
363
+ )}
364
+
365
+ <button type="submit">Save</button>
366
+ </form>
367
+ );
368
+ }
369
+ ```
370
+
371
+ ### Render All Fields Dynamically
372
+
373
+ Generate form fields from schema configuration.
374
+
375
+ ```tsx
376
+ function DynamicForm() {
377
+ const product = new Product(Roles.Seller);
378
+
379
+ const form = useForm<SellerProduct>({
380
+ source: product._id,
381
+ operation: "create",
382
+ });
383
+
384
+ if (form.isLoading) return <div>Loading...</div>;
385
+
386
+ const fields = form.getFields();
387
+
388
+ return (
389
+ <form onSubmit={form.handleSubmit()}>
390
+ {Object.entries(fields).map(([fieldName, field]) => {
391
+ if (field.permission.hidden) return null;
392
+
393
+ return (
394
+ <div key={fieldName}>
395
+ <label>{field.label}</label>
396
+ <input
397
+ {...form.register(fieldName as keyof SellerProduct)}
398
+ type={field.type === "number" ? "number" : "text"}
399
+ disabled={!field.permission.editable || field.computed}
400
+ />
401
+ </div>
402
+ );
403
+ })}
404
+
405
+ <button type="submit">Submit</button>
406
+ </form>
407
+ );
408
+ }
409
+ ```
410
+
411
+ ---
412
+
413
+ ## Validation
414
+
415
+ ### Displaying Field Errors
416
+
417
+ Show validation errors inline with fields.
418
+
419
+ ```tsx
420
+ function FormWithValidation() {
421
+ const product = new Product(Roles.Seller);
422
+
423
+ const form = useForm<SellerProduct>({
424
+ source: product._id,
425
+ operation: "create",
426
+ mode: "onBlur", // Validate on blur
427
+ });
428
+
429
+ return (
430
+ <form onSubmit={form.handleSubmit()}>
431
+ <div className={form.errors.Title ? "field-error" : ""}>
432
+ <label>Title *</label>
433
+ <input {...form.register("Title")} />
434
+ {form.errors.Title && (
435
+ <span className="error-message">{form.errors.Title.message}</span>
435
436
  )}
437
+ </div>
436
438
 
437
- {error && <span className="error">{error.message as string}</span>}
439
+ <div className={form.errors.Price ? "field-error" : ""}>
440
+ <label>Price *</label>
441
+ <input type="number" {...form.register("Price")} />
442
+ {form.errors.Price && (
443
+ <span className="error-message">{form.errors.Price.message}</span>
444
+ )}
438
445
  </div>
439
- );
440
- };
441
446
 
442
- // Access raw BDO schema
443
- const displayRawSchema = () => {
444
- const schema: BDOSchemaType | null = form.schema;
445
- if (!schema) return null;
447
+ <button type="submit" disabled={!form.isValid}>
448
+ Submit
449
+ </button>
450
+ </form>
451
+ );
452
+ }
453
+ ```
446
454
 
447
- return (
448
- <details>
449
- <summary>Raw Schema: {schema.Name}</summary>
450
- <ul>
451
- {Object.entries(schema.Fields).map(
452
- ([fieldName, field]: [string, BDOFieldDefinitionType]) => (
453
- <li key={fieldName}>
454
- {field.Name || fieldName} ({field.Type})
455
- {field.Required && " - Required"}
456
- {field.Computed && " - Computed"}
457
- </li>
458
- ),
459
- )}
460
- </ul>
461
- </details>
462
- );
455
+ ### Form State Indicators
456
+
457
+ Show form status to users.
458
+
459
+ ```tsx
460
+ function FormWithStateIndicators() {
461
+ const product = new Product(Roles.Seller);
462
+
463
+ const form = useForm<SellerProduct>({
464
+ source: product._id,
465
+ operation: "update",
466
+ recordId: "PROD-001",
467
+ });
468
+
469
+ return (
470
+ <form onSubmit={form.handleSubmit()}>
471
+ {/* Form fields */}
472
+ <input {...form.register("Title")} />
473
+ <input type="number" {...form.register("Price")} />
474
+
475
+ {/* Status bar */}
476
+ <div className="form-status">
477
+ {form.isDirty && <span className="unsaved">Unsaved changes</span>}
478
+ {!form.isValid && <span className="invalid">Please fix errors</span>}
479
+ {form.isSubmitting && <span className="saving">Saving...</span>}
480
+ {form.isSubmitSuccessful && <span className="success">Saved!</span>}
481
+ </div>
482
+
483
+ <button type="submit" disabled={form.isSubmitting || !form.isDirty}>
484
+ Save Changes
485
+ </button>
486
+
487
+ <button
488
+ type="button"
489
+ onClick={() => form.reset()}
490
+ disabled={!form.isDirty}
491
+ >
492
+ Discard Changes
493
+ </button>
494
+ </form>
495
+ );
496
+ }
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Submission
502
+
503
+ ### Handle Submit with Callbacks
504
+
505
+ Process successful submissions and errors.
506
+
507
+ ```tsx
508
+ function FormWithCallbacks() {
509
+ const product = new Product(Roles.Seller);
510
+
511
+ const form = useForm<SellerProduct>({
512
+ source: product._id,
513
+ operation: "create",
514
+ });
515
+
516
+ const onSuccess = (data: SellerProduct) => {
517
+ alert(`Product "${data.Title}" created successfully!`);
518
+ form.reset();
463
519
  };
464
520
 
465
- // Check field properties using helpers
466
- const checkFieldStatus = () => {
467
- console.log("Is Title required?", form.isFieldRequired("Title"));
468
- console.log("Is Discount computed?", form.isFieldComputed("Discount"));
469
- console.log("Has Category field?", form.hasField("Category"));
470
- console.log("All computed fields:", form.computedFields);
471
- console.log("All required fields:", form.requiredFields);
521
+ const onError = (error: FieldErrors<SellerProduct> | Error) => {
522
+ if (error instanceof Error) {
523
+ // API error
524
+ alert(`Server error: ${error.message}`);
525
+ } else {
526
+ // Validation errors
527
+ const fieldNames = Object.keys(error).join(", ");
528
+ alert(`Please fix errors in: ${fieldNames}`);
529
+ }
472
530
  };
473
531
 
474
- // Handle form submission - use the new RHF-style API
475
- // Note: form.handleSubmit returns a function that handles preventDefault internally
532
+ return (
533
+ <form onSubmit={form.handleSubmit(onSuccess, onError)}>
534
+ <input {...form.register("Title")} placeholder="Title" />
535
+ <input type="number" {...form.register("Price")} placeholder="Price" />
536
+ <button type="submit">Create</button>
537
+ </form>
538
+ );
539
+ }
540
+ ```
541
+
542
+ ### Programmatic Submission
476
543
 
477
- // Open create form
478
- const handleCreate = () => {
479
- setFormMode("create");
480
- setSelectedProduct(null);
481
- setShowForm(true);
482
- };
544
+ Submit form without a submit button.
545
+
546
+ ```tsx
547
+ function FormWithProgrammaticSubmit() {
548
+ const product = new Product(Roles.Seller);
549
+
550
+ const form = useForm<SellerProduct>({
551
+ source: product._id,
552
+ operation: "create",
553
+ });
554
+
555
+ const handleSaveAndClose = async () => {
556
+ const submitHandler = form.handleSubmit(
557
+ (data) => {
558
+ console.log("Saved:", data);
559
+ // Close modal or navigate away
560
+ },
561
+ (error) => {
562
+ console.error("Validation failed:", error);
563
+ },
564
+ );
483
565
 
484
- // Open edit form
485
- const handleEdit = (productItem: SellerProduct) => {
486
- setFormMode("update");
487
- setSelectedProduct(productItem);
488
- setShowForm(true);
566
+ await submitHandler();
489
567
  };
490
568
 
491
569
  return (
492
- <div className="product-management">
493
- <h1>Products</h1>
494
- <button onClick={handleCreate}>Add Product</button>
495
-
496
- {/* Product Table */}
497
- <table>
498
- <thead>
499
- <tr>
500
- <th>Title</th>
501
- <th>Price</th>
502
- <th>Discount</th>
503
- <th>Actions</th>
504
- </tr>
505
- </thead>
506
- <tbody>
507
- {table.rows.map((row: SellerProduct) => (
508
- <tr key={row._id}>
509
- <td>{row.Title}</td>
510
- <td>${row.Price.toFixed(2)}</td>
511
- <td>{row.Discount}%</td>
512
- <td>
513
- <button onClick={() => handleEdit(row)}>Edit</button>
514
- </td>
515
- </tr>
516
- ))}
517
- </tbody>
518
- </table>
519
-
520
- {/* Form Dialog */}
521
- {showForm && (
522
- <dialog open>
523
- {form.isLoadingInitialData ? (
524
- <div>Loading form...</div>
525
- ) : form.loadError ? (
526
- <div>
527
- <p>Error: {form.loadError.message}</p>
528
- <button onClick={() => form.refreshSchema()}>Retry</button>
529
- </div>
530
- ) : (
531
- <form onSubmit={form.handleSubmit(onFormSuccess, onFormError)}>
532
- <h2>
533
- {formMode === "create" ? "Create Product" : "Edit Product"}
534
- </h2>
535
-
536
- {/* Schema Info */}
537
- {displaySchemaInfo()}
538
-
539
- {/* Form Fields */}
540
- {renderField("Title")}
541
- {renderField("Description")}
542
- {renderField("MRP")}
543
- {renderField("Price")}
544
- {renderField("Discount")}
545
- {renderField("Category")}
546
- {renderField("Stock")}
547
-
548
- {/* Form Status */}
549
- <div className="form-status">
550
- {form.isDirty && <span>Unsaved changes</span>}
551
- {!form.isValid && <span>Please fix validation errors</span>}
552
- </div>
553
-
554
- {/* Cross-field validation errors */}
555
- {form.errors.root && (
556
- <div className="cross-field-errors">
557
- {Object.values(form.errors.root).map((err, i) => (
558
- <p key={i} className="error">
559
- {(err as any).message}
560
- </p>
561
- ))}
562
- </div>
563
- )}
564
-
565
- {/* Note: Submit errors are now handled via onError callback */}
566
-
567
- {/* Raw Schema Debug */}
568
- {displayRawSchema()}
569
-
570
- {/* Form Actions */}
571
- <div className="form-actions">
572
- <button type="button" onClick={() => setShowForm(false)}>
573
- Cancel
574
- </button>
575
- <button type="button" onClick={() => form.reset()}>
576
- Reset
577
- </button>
578
- <button type="button" onClick={() => form.validateForm()}>
579
- Validate
580
- </button>
581
- <button
582
- type="submit"
583
- disabled={form.isSubmitting || form.isLoading}
584
- >
585
- {form.isSubmitting
586
- ? "Saving..."
587
- : formMode === "create"
588
- ? "Create"
589
- : "Update"}
590
- </button>
591
- </div>
592
-
593
- {/* Watch specific fields */}
594
- <div className="field-preview">
595
- <p>Current Price: ${form.watch("Price")}</p>
596
- <p>Current Discount: {form.watch("Discount")}%</p>
597
- </div>
598
- </form>
599
- )}
600
- </dialog>
601
- )}
570
+ <div>
571
+ <input {...form.register("Title")} />
572
+ <input type="number" {...form.register("Price")} />
573
+
574
+ <div className="actions">
575
+ <button type="button" onClick={() => form.reset()}>
576
+ Cancel
577
+ </button>
578
+ <button type="button" onClick={handleSaveAndClose}>
579
+ Save & Close
580
+ </button>
581
+ </div>
602
582
  </div>
603
583
  );
604
584
  }
605
585
  ```
606
586
 
587
+ ---
588
+
589
+ ## Watch and setValue
590
+
591
+ ### Watch Field Values
592
+
593
+ React to field value changes in real-time.
594
+
595
+ ```tsx
596
+ function FormWithWatcher() {
597
+ const product = new Product(Roles.Seller);
598
+
599
+ const form = useForm<SellerProduct>({
600
+ source: product._id,
601
+ operation: "create",
602
+ });
603
+
604
+ const mrp = form.watch("MRP");
605
+ const price = form.watch("Price");
606
+ const discount = mrp > 0 ? (((mrp - price) / mrp) * 100).toFixed(1) : 0;
607
+
608
+ return (
609
+ <form onSubmit={form.handleSubmit()}>
610
+ <div>
611
+ <label>MRP</label>
612
+ <input type="number" {...form.register("MRP")} />
613
+ </div>
614
+
615
+ <div>
616
+ <label>Price</label>
617
+ <input type="number" {...form.register("Price")} />
618
+ </div>
619
+
620
+ <div className="preview">
621
+ <p>Discount: {discount}%</p>
622
+ <p>You save: ${(mrp - price).toFixed(2)}</p>
623
+ </div>
624
+
625
+ <button type="submit">Save</button>
626
+ </form>
627
+ );
628
+ }
629
+ ```
630
+
631
+ ### Set Field Values Programmatically
632
+
633
+ Update field values from external actions.
634
+
635
+ ```tsx
636
+ function FormWithSetValue() {
637
+ const product = new Product(Roles.Seller);
638
+
639
+ const form = useForm<SellerProduct>({
640
+ source: product._id,
641
+ operation: "create",
642
+ });
643
+
644
+ const applyTemplate = (template: string) => {
645
+ switch (template) {
646
+ case "electronics":
647
+ form.setValue("Category", "Electronics");
648
+ form.setValue("Price", 99.99);
649
+ break;
650
+ case "books":
651
+ form.setValue("Category", "Books");
652
+ form.setValue("Price", 19.99);
653
+ break;
654
+ }
655
+ };
656
+
657
+ return (
658
+ <form onSubmit={form.handleSubmit()}>
659
+ <div className="templates">
660
+ <button type="button" onClick={() => applyTemplate("electronics")}>
661
+ Electronics Template
662
+ </button>
663
+ <button type="button" onClick={() => applyTemplate("books")}>
664
+ Books Template
665
+ </button>
666
+ </div>
667
+
668
+ <input {...form.register("Title")} placeholder="Title" />
669
+ <input {...form.register("Category")} placeholder="Category" />
670
+ <input type="number" {...form.register("Price")} placeholder="Price" />
671
+
672
+ <button type="submit">Create</button>
673
+ </form>
674
+ );
675
+ }
676
+ ```
677
+
678
+ ---
679
+
607
680
  ## Error Utilities
608
681
 
682
+ The SDK provides utilities to help handle and categorize errors from form operations.
683
+
684
+ ### Available Utilities
685
+
609
686
  ```typescript
610
687
  import {
611
688
  parseApiError,
@@ -613,17 +690,151 @@ import {
613
690
  isValidationError,
614
691
  clearFormCache,
615
692
  } from "@ram_28/kf-ai-sdk/form";
693
+ ```
694
+
695
+ | Utility | Description |
696
+ | -------------------------- | ---------------------------------------------------------------- |
697
+ | `parseApiError(error)` | Extracts a user-friendly message from any error type |
698
+ | `isNetworkError(error)` | Returns `true` if error is network-related (connection, timeout) |
699
+ | `isValidationError(error)` | Returns `true` if error is a validation/schema error |
700
+ | `clearFormCache()` | Clears the schema cache (30-minute TTL by default) |
701
+
702
+ ### Parse and Handle API Errors
703
+
704
+ Use built-in utilities for comprehensive error handling.
705
+
706
+ ```tsx
707
+ import {
708
+ parseApiError,
709
+ isNetworkError,
710
+ isValidationError,
711
+ clearFormCache,
712
+ } from "@ram_28/kf-ai-sdk/form";
713
+
714
+ function FormWithErrorHandling() {
715
+ const product = new Product(Roles.Seller);
716
+
717
+ const form = useForm<SellerProduct>({
718
+ source: product._id,
719
+ operation: "create",
720
+ });
721
+
722
+ const onError = (error: Error) => {
723
+ const message = parseApiError(error);
616
724
 
617
- // Parse API error to user-friendly message
618
- const message = parseApiError(error); // "Failed to save: Invalid data"
725
+ if (isNetworkError(error)) {
726
+ alert("Network error. Please check your connection.");
727
+ } else if (isValidationError(error)) {
728
+ alert("Validation failed. Please check your input.");
729
+ } else {
730
+ alert(message);
731
+ }
732
+ };
733
+
734
+ const handleClearCache = () => {
735
+ clearFormCache();
736
+ window.location.reload();
737
+ };
738
+
739
+ return (
740
+ <form onSubmit={form.handleSubmit(undefined, onError)}>
741
+ {/* form fields */}
742
+ <button type="submit">Save</button>
743
+ <button type="button" onClick={handleClearCache}>
744
+ Clear Cache & Reload
745
+ </button>
746
+ </form>
747
+ );
748
+ }
749
+ ```
750
+
751
+ ### Handling Different Error Types
752
+
753
+ The `handleSubmit` callback receives different error types depending on the failure stage:
754
+
755
+ ```tsx
756
+ function FormWithDetailedErrorHandling() {
757
+ const product = new Product(Roles.Seller);
758
+
759
+ const form = useForm<SellerProduct>({
760
+ source: product._id,
761
+ operation: "create",
762
+ });
763
+
764
+ const onSubmit = (data: SellerProduct) => {
765
+ console.log("Success:", data);
766
+ };
767
+
768
+ const onError = (
769
+ error: FieldErrors<SellerProduct> | Error,
770
+ event?: React.BaseSyntheticEvent,
771
+ ) => {
772
+ // Check if it's a validation error (from react-hook-form)
773
+ if (!(error instanceof Error)) {
774
+ // Field validation errors - error is FieldErrors<SellerProduct>
775
+ const invalidFields = Object.keys(error);
776
+ console.log("Validation failed for fields:", invalidFields);
777
+
778
+ // Access specific field errors
779
+ if (error.Title) {
780
+ console.log("Title error:", error.Title.message);
781
+ }
782
+ return;
783
+ }
619
784
 
620
- // Check error type for specific handling
621
- if (isNetworkError(error)) {
622
- showOfflineMessage();
623
- } else if (isValidationError(error)) {
624
- showValidationErrors();
785
+ // API/Network error - error is Error
786
+ if (isNetworkError(error)) {
787
+ // Handle offline, timeout, connection refused
788
+ console.error("Network issue:", error.message);
789
+ } else if (isValidationError(error)) {
790
+ // Server-side validation error
791
+ console.error("Server validation:", parseApiError(error));
792
+ } else {
793
+ // Other server errors (500, etc.)
794
+ console.error("Server error:", parseApiError(error));
795
+ }
796
+ };
797
+
798
+ return (
799
+ <form onSubmit={form.handleSubmit(onSubmit, onError)}>
800
+ {/* form fields */}
801
+ </form>
802
+ );
625
803
  }
804
+ ```
805
+
806
+ ### Schema Loading Errors
807
+
808
+ Handle errors when the form schema fails to load.
809
+
810
+ ```tsx
811
+ function FormWithSchemaErrorHandling() {
812
+ const product = new Product(Roles.Seller);
813
+
814
+ const form = useForm<SellerProduct>({
815
+ source: product._id,
816
+ operation: "create",
817
+ onSchemaError: (error) => {
818
+ console.error("Failed to load form schema:", error.message);
819
+ // Optionally clear cache and retry
820
+ clearFormCache();
821
+ },
822
+ });
626
823
 
627
- // Clear form schema cache (useful after schema changes)
628
- clearFormCache();
824
+ // Check for load errors
825
+ if (form.loadError) {
826
+ return (
827
+ <div className="error-state">
828
+ <p>Failed to load form: {form.loadError.message}</p>
829
+ <button onClick={() => window.location.reload()}>Retry</button>
830
+ </div>
831
+ );
832
+ }
833
+
834
+ if (form.isLoading) {
835
+ return <div>Loading form...</div>;
836
+ }
837
+
838
+ return <form onSubmit={form.handleSubmit()}>{/* form fields */}</form>;
839
+ }
629
840
  ```