@ram_28/kf-ai-sdk 1.0.9 → 1.0.10

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": "@ram_28/kf-ai-sdk",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Type-safe, AI-driven SDK for building modern web applications with role-based access control",
5
5
  "author": "Ramprasad",
6
6
  "license": "MIT",
@@ -6,8 +6,6 @@ import type {
6
6
  UseFormRegister,
7
7
  Mode,
8
8
  FieldValues as RHFFieldValues,
9
- SubmitHandler,
10
- SubmitErrorHandler,
11
9
  FieldErrors,
12
10
  Path,
13
11
  PathValue,
@@ -21,15 +19,23 @@ import type {
21
19
  // ============================================================
22
20
 
23
21
  /**
24
- * Extended handleSubmit that allows optional callbacks
25
- * When no callback is provided, uses the SDK's built-in submit function
22
+ * HandleSubmit follows React Hook Form's signature pattern
23
+ *
24
+ * - onSuccess: Called with the API response data on successful submission
25
+ * - onError: Called with either FieldErrors (validation failed) or Error (API failed)
26
+ *
27
+ * Internal flow:
28
+ * 1. RHF validation + Cross-field validation → FAILS → onError(fieldErrors)
29
+ * 2. Clean data & call API → FAILS → onError(apiError)
30
+ * 3. SUCCESS → onSuccess(responseData)
26
31
  */
27
- export type ExtendedHandleSubmit<T extends RHFFieldValues> = {
28
- (
29
- onValid?: SubmitHandler<T>,
30
- onInvalid?: SubmitErrorHandler<T>
31
- ): (e?: React.BaseSyntheticEvent) => Promise<void>;
32
- };
32
+ export type HandleSubmit<T extends RHFFieldValues> = (
33
+ onSuccess?: (data: T, e?: React.BaseSyntheticEvent) => void | Promise<void>,
34
+ onError?: (
35
+ error: FieldErrors<T> | Error,
36
+ e?: React.BaseSyntheticEvent
37
+ ) => void | Promise<void>
38
+ ) => (e?: React.BaseSyntheticEvent) => Promise<void>;
33
39
 
34
40
  // ============================================================
35
41
  // EXPRESSION TREE TYPES
@@ -276,18 +282,9 @@ export interface UseFormOptions<
276
282
  /** User role for permission enforcement */
277
283
  userRole?: string;
278
284
 
279
- /** Success callback */
280
- onSuccess?: (data: T) => void;
281
-
282
- /** Error callback */
283
- onError?: (error: Error) => void;
284
-
285
- /** Schema load error callback */
285
+ /** Schema load error callback (separate concern from form submission) */
286
286
  onSchemaError?: (error: Error) => void;
287
287
 
288
- /** Submit error callback */
289
- onSubmitError?: (error: Error) => void;
290
-
291
288
  /** Skip schema fetching (use for testing) */
292
289
  skipSchemaFetch?: boolean;
293
290
 
@@ -448,8 +445,38 @@ export interface UseFormReturn<
448
445
  options?: RegisterOptions<T, K>
449
446
  ) => ReturnType<UseFormRegister<T>>;
450
447
 
451
- /** Handle form submission - automatically uses SDK's submit logic */
452
- handleSubmit: () => (e?: React.BaseSyntheticEvent) => Promise<void>;
448
+ /**
449
+ * Handle form submission with optional callbacks
450
+ *
451
+ * @example
452
+ * // Basic usage - no callbacks
453
+ * <form onSubmit={form.handleSubmit()}>
454
+ *
455
+ * @example
456
+ * // With success callback
457
+ * <form onSubmit={form.handleSubmit((data) => {
458
+ * toast.success("Saved!");
459
+ * navigate("/products/" + data._id);
460
+ * })}>
461
+ *
462
+ * @example
463
+ * // With both callbacks
464
+ * <form onSubmit={form.handleSubmit(
465
+ * (data) => toast.success("Saved!"),
466
+ * (error) => {
467
+ * if (error instanceof Error) {
468
+ * toast.error(error.message); // API error
469
+ * } else {
470
+ * toast.error("Please fix the form errors"); // Validation errors
471
+ * }
472
+ * }
473
+ * )}>
474
+ *
475
+ * @example
476
+ * // Programmatic submission
477
+ * await form.handleSubmit(onSuccess, onError)();
478
+ */
479
+ handleSubmit: HandleSubmit<T>;
453
480
 
454
481
  /** Watch field values with strict typing */
455
482
  watch: <K extends Path<T> | readonly Path<T>[]>(
@@ -516,9 +543,6 @@ export interface UseFormReturn<
516
543
  /** Schema fetch error */
517
544
  loadError: Error | null;
518
545
 
519
- /** Form submission error */
520
- submitError: Error | null;
521
-
522
546
  /** Any error active */
523
547
  hasError: boolean;
524
548
 
@@ -561,9 +585,6 @@ export interface UseFormReturn<
561
585
  // OPERATIONS
562
586
  // ============================================================
563
587
 
564
- /** Submit form manually */
565
- submit: () => Promise<void>;
566
-
567
588
  /** Refresh schema */
568
589
  refreshSchema: () => Promise<void>;
569
590
 
@@ -49,10 +49,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
49
49
  mode = "onBlur", // Validation mode - controls when errors are shown (see types.ts for details)
50
50
  enabled = true,
51
51
  userRole,
52
- onSuccess,
53
- onError,
54
52
  onSchemaError,
55
- onSubmitError,
56
53
  skipSchemaFetch = false,
57
54
  schema: manualSchema,
58
55
  draftOnEveryChange = false,
@@ -65,7 +62,6 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
65
62
  const [schemaConfig, setSchemaConfig] =
66
63
  useState<FormSchemaConfig | null>(null);
67
64
  const [referenceData, setReferenceData] = useState<Record<string, any[]>>({});
68
- const [submitError, setSubmitError] = useState<Error | null>(null);
69
65
  const [isSubmitting, setIsSubmitting] = useState(false);
70
66
  const [lastFormValues] = useState<Partial<T>>({});
71
67
 
@@ -77,25 +73,10 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
77
73
  // This allows us to detect changes since the last draft, not since form init
78
74
  const lastSyncedValuesRef = useRef<Partial<T> | null>(null);
79
75
 
80
- // Stable callback refs to prevent dependency loops
81
- const onSuccessRef = useRef(onSuccess);
82
- const onErrorRef = useRef(onError);
83
- const onSubmitErrorRef = useRef(onSubmitError);
76
+ // Stable callback ref to prevent dependency loops
84
77
  const onSchemaErrorRef = useRef(onSchemaError);
85
78
 
86
- // Update refs when callbacks change
87
- useEffect(() => {
88
- onSuccessRef.current = onSuccess;
89
- }, [onSuccess]);
90
-
91
- useEffect(() => {
92
- onErrorRef.current = onError;
93
- }, [onError]);
94
-
95
- useEffect(() => {
96
- onSubmitErrorRef.current = onSubmitError;
97
- }, [onSubmitError]);
98
-
79
+ // Update ref when callback changes
99
80
  useEffect(() => {
100
81
  onSchemaErrorRef.current = onSchemaError;
101
82
  }, [onSchemaError]);
@@ -201,19 +182,13 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
201
182
  }
202
183
  }, [schema, userRole]); // Removed onSchemaError - using ref instead
203
184
 
204
- // Handle schema and record errors
185
+ // Handle schema error
205
186
  useEffect(() => {
206
187
  if (schemaError) {
207
188
  onSchemaErrorRef.current?.(schemaError);
208
189
  }
209
190
  }, [schemaError]);
210
191
 
211
- useEffect(() => {
212
- if (recordError) {
213
- onErrorRef.current?.(recordError);
214
- }
215
- }, [recordError]);
216
-
217
192
  // ============================================================
218
193
  // COMPUTED FIELD DEPENDENCY TRACKING AND OPTIMIZATION
219
194
  // ============================================================
@@ -495,76 +470,117 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
495
470
  ]);
496
471
 
497
472
  // ============================================================
498
- // FORM SUBMISSION
473
+ // HANDLE SUBMIT - RHF-style API with internal submission
499
474
  // ============================================================
500
475
 
501
- const submit = useCallback(async (): Promise<void> => {
502
- if (!schemaConfig) {
503
- throw new Error("Schema not loaded");
504
- }
505
-
506
- setIsSubmitting(true);
507
- setSubmitError(null);
476
+ /**
477
+ * handleSubmit follows React Hook Form's signature pattern:
478
+ *
479
+ * handleSubmit(onSuccess?, onError?) => (e?) => Promise<void>
480
+ *
481
+ * Internal flow:
482
+ * 1. RHF validation + Cross-field validation → FAILS → onError(fieldErrors)
483
+ * 2. Clean data & call API → FAILS → onError(apiError)
484
+ * 3. SUCCESS → onSuccess(responseData)
485
+ */
486
+ const handleSubmit = useCallback(
487
+ (
488
+ onSuccess?: (data: T, e?: React.BaseSyntheticEvent) => void | Promise<void>,
489
+ onError?: (
490
+ error: import("react-hook-form").FieldErrors<T> | Error,
491
+ e?: React.BaseSyntheticEvent
492
+ ) => void | Promise<void>
493
+ ) => {
494
+ return rhfForm.handleSubmit(
495
+ // RHF onValid handler - validation passed, now do cross-field + API
496
+ async (data, event) => {
497
+ if (!schemaConfig) {
498
+ const error = new Error("Schema not loaded");
499
+ onError?.(error, event);
500
+ return;
501
+ }
508
502
 
509
- try {
510
- // Validate form including cross-field validation
511
- const isValid = await validateForm();
512
- if (!isValid) {
513
- throw new Error("Form validation failed");
514
- }
503
+ setIsSubmitting(true);
515
504
 
516
- const formValues = rhfForm.getValues();
505
+ try {
506
+ // Cross-field validation
507
+ const transformedRules = schemaConfig.crossFieldValidation.map(
508
+ (rule) => ({
509
+ Id: rule.Id,
510
+ Condition: { ExpressionTree: rule.ExpressionTree },
511
+ Message: rule.Message || `Validation failed for ${rule.Name}`,
512
+ })
513
+ );
517
514
 
518
- // Clean data for submission
519
- // - For create: includes all non-computed fields
520
- // - For update: includes only fields that changed from recordData
521
- const cleanedData = cleanFormData(
522
- formValues as any,
523
- schemaConfig.computedFields,
524
- operation,
525
- recordData as Partial<T> | undefined
526
- );
515
+ const crossFieldErrors = validateCrossField(
516
+ transformedRules,
517
+ data as any,
518
+ referenceData
519
+ );
527
520
 
528
- // Submit data
529
- const result = await submitFormData<T>(
530
- source,
531
- operation,
532
- cleanedData,
533
- recordId
534
- );
521
+ if (crossFieldErrors.length > 0) {
522
+ // Set cross-field errors in form state
523
+ crossFieldErrors.forEach((error, index) => {
524
+ rhfForm.setError(`root.crossField${index}` as any, {
525
+ type: "validate",
526
+ message: error.message,
527
+ });
528
+ });
529
+ // Call onError with current form errors
530
+ onError?.(rhfForm.formState.errors, event);
531
+ return;
532
+ }
535
533
 
536
- if (!result.success) {
537
- throw result.error || new Error("Submission failed");
538
- }
534
+ // Clean data for submission
535
+ const cleanedData = cleanFormData(
536
+ data as any,
537
+ schemaConfig.computedFields,
538
+ operation,
539
+ recordData as Partial<T> | undefined
540
+ );
539
541
 
540
- // Success callback
541
- onSuccessRef.current?.(result.data || formValues);
542
+ // Submit data to API
543
+ const result = await submitFormData<T>(
544
+ source,
545
+ operation,
546
+ cleanedData,
547
+ recordId
548
+ );
542
549
 
543
- // Reset form for create operations
544
- if (operation === "create") {
545
- rhfForm.reset();
546
- }
547
- } catch (error) {
548
- const submitError = error as Error;
549
- setSubmitError(submitError);
550
- onSubmitErrorRef.current?.(submitError);
551
- onErrorRef.current?.(submitError);
552
- throw error;
553
- } finally {
554
- setIsSubmitting(false);
555
- }
556
- }, [schemaConfig, validateForm, rhfForm, source, operation, recordId, recordData]);
550
+ if (!result.success) {
551
+ throw result.error || new Error("Submission failed");
552
+ }
557
553
 
558
- // ============================================================
559
- // HANDLE SUBMIT - Simplified API
560
- // ============================================================
554
+ // Reset form for create operations
555
+ if (operation === "create") {
556
+ rhfForm.reset();
557
+ }
561
558
 
562
- // Simplified handleSubmit that always uses SDK's submit function
563
- const handleSubmit = useCallback(() => {
564
- return rhfForm.handleSubmit(async () => {
565
- await submit();
566
- });
567
- }, [rhfForm, submit]);
559
+ // Success callback with response data
560
+ await onSuccess?.(result.data || data, event);
561
+ } catch (error) {
562
+ // API error - call onError with Error object
563
+ onError?.(error as Error, event);
564
+ } finally {
565
+ setIsSubmitting(false);
566
+ }
567
+ },
568
+ // RHF onInvalid handler - validation failed
569
+ (errors, event) => {
570
+ onError?.(errors, event);
571
+ }
572
+ );
573
+ },
574
+ [
575
+ rhfForm,
576
+ schemaConfig,
577
+ referenceData,
578
+ source,
579
+ operation,
580
+ recordId,
581
+ recordData,
582
+ ]
583
+ );
568
584
 
569
585
  // ============================================================
570
586
  // FIELD HELPERS
@@ -623,7 +639,6 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
623
639
 
624
640
  const clearErrors = useCallback((): void => {
625
641
  rhfForm.clearErrors();
626
- setSubmitError(null);
627
642
  }, [rhfForm]);
628
643
 
629
644
  // ============================================================
@@ -634,7 +649,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
634
649
  isLoadingSchema || (operation === "update" && isLoadingRecord);
635
650
  const isLoading = isLoadingInitialData || isSubmitting;
636
651
  const loadError = schemaError || recordError;
637
- const hasError = !!(loadError || submitError);
652
+ const hasError = !!loadError;
638
653
 
639
654
  const computedFields = useMemo<Array<keyof T>>(
640
655
  () => (schemaConfig?.computedFields as Array<keyof T>) || [],
@@ -796,7 +811,6 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
796
811
 
797
812
  // Error handling
798
813
  loadError: loadError as Error | null,
799
- submitError,
800
814
  hasError,
801
815
 
802
816
  // Schema information
@@ -813,7 +827,6 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
813
827
  isFieldComputed,
814
828
 
815
829
  // Operations
816
- submit,
817
830
  refreshSchema,
818
831
  validateForm,
819
832
  clearErrors,