@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ram_28/kf-ai-sdk",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
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",
@@ -57,18 +57,21 @@ export interface ConditionGroupBuilder {
57
57
 
58
58
  /**
59
59
  * Hook options (minimal configuration)
60
+ * Used for initializing useFilter, and also for initialState in useTable/useKanban
61
+ * @template T - Data type for type-safe field names (defaults to any)
60
62
  */
61
- export interface UseFilterOptionsType {
62
- /** Initial filter conditions */
63
- initialConditions?: Array<ConditionType | ConditionGroupType>;
64
- /** Initial operator for combining conditions (defaults to "And") */
65
- initialOperator?: ConditionGroupOperatorType;
63
+ export interface UseFilterOptionsType<T = any> {
64
+ /** Filter conditions */
65
+ conditions?: Array<ConditionType<T> | ConditionGroupType<T>>;
66
+ /** Operator for combining conditions (defaults to "And") */
67
+ operator?: ConditionGroupOperatorType;
66
68
  }
67
69
 
68
70
  /**
69
71
  * Hook return interface with nested filter support
72
+ * @template T - Data type for type-safe field names (defaults to any)
70
73
  */
71
- export interface UseFilterReturnType {
74
+ export interface UseFilterReturnType<T = any> {
72
75
  // ============================================================
73
76
  // STATE (read-only)
74
77
  // ============================================================
@@ -77,10 +80,10 @@ export interface UseFilterReturnType {
77
80
  operator: ConditionGroupOperatorType;
78
81
 
79
82
  /** Current filter items (with id populated) */
80
- items: Array<ConditionType | ConditionGroupType>;
83
+ items: Array<ConditionType<T> | ConditionGroupType<T>>;
81
84
 
82
85
  /** Ready-to-use API payload (id stripped, undefined if no conditions) */
83
- payload: FilterType | undefined;
86
+ payload: FilterType<T> | undefined;
84
87
 
85
88
  /** Whether any conditions exist */
86
89
  hasConditions: boolean;
@@ -95,7 +98,7 @@ export interface UseFilterReturnType {
95
98
  * @param parentId - Optional id of the parent ConditionGroup. If omitted, adds at root level
96
99
  * @returns The id of the created condition
97
100
  */
98
- addCondition: (condition: Omit<ConditionType, "id">, parentId?: string) => string;
101
+ addCondition: (condition: Omit<ConditionType<T>, "id">, parentId?: string) => string;
99
102
 
100
103
  /**
101
104
  * Add a condition group at root level or to a specific parent group
@@ -114,7 +117,7 @@ export interface UseFilterReturnType {
114
117
  * @param id - The id of the condition to update
115
118
  * @param updates - Partial updates to apply
116
119
  */
117
- updateCondition: (id: string, updates: Partial<Omit<ConditionType, "id">>) => void;
120
+ updateCondition: (id: string, updates: Partial<Omit<ConditionType<T>, "id">>) => void;
118
121
 
119
122
  /**
120
123
  * Update a condition group's operator by id
@@ -138,7 +141,7 @@ export interface UseFilterReturnType {
138
141
  * @param id - The id to look up
139
142
  * @returns The item or undefined if not found
140
143
  */
141
- getCondition: (id: string) => ConditionType | ConditionGroupType | undefined;
144
+ getCondition: (id: string) => ConditionType<T> | ConditionGroupType<T> | undefined;
142
145
 
143
146
  // ============================================================
144
147
  // UTILITY
@@ -16,9 +16,11 @@ let idCounter = 0;
16
16
 
17
17
  /**
18
18
  * Generate a unique ID for conditions and groups
19
+ * Uses timestamp + random component + counter to prevent collisions
19
20
  */
20
21
  const generateId = (): string => {
21
- return `filter_${Date.now()}_${++idCounter}`;
22
+ const random = Math.random().toString(36).substring(2, 9);
23
+ return `filter_${Date.now()}_${random}_${++idCounter}`;
22
24
  };
23
25
 
24
26
  /**
@@ -178,22 +180,22 @@ const addToParent = (
178
180
  // USE FILTER HOOK - Nested Filter Support
179
181
  // ============================================================
180
182
 
181
- export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnType {
183
+ export function useFilter<T = any>(options: UseFilterOptionsType<T> = {}): UseFilterReturnType<T> {
182
184
  // Initialize items with ids
183
- const [items, setItems] = useState<Array<ConditionType | ConditionGroupType>>(() =>
184
- cloneWithIds(options.initialConditions || [])
185
+ const [items, setItems] = useState<Array<ConditionType<T> | ConditionGroupType<T>>>(() =>
186
+ cloneWithIds(options.conditions || []) as Array<ConditionType<T> | ConditionGroupType<T>>
185
187
  );
186
188
 
187
189
  const [operator, setOperatorState] = useState<ConditionGroupOperatorType>(
188
- options.initialOperator || "And"
190
+ options.operator || "And"
189
191
  );
190
192
 
191
193
  // Build payload for API (strip ids)
192
- const payload = useMemo((): FilterType | undefined => {
194
+ const payload = useMemo((): FilterType<T> | undefined => {
193
195
  if (items.length === 0) return undefined;
194
196
  return {
195
197
  Operator: operator,
196
- Condition: stripIds(items),
198
+ Condition: stripIds(items) as Array<ConditionType<T> | ConditionGroupType<T>>,
197
199
  };
198
200
  }, [items, operator]);
199
201
 
@@ -204,11 +206,11 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
204
206
  // ============================================================
205
207
 
206
208
  const addCondition = useCallback(
207
- (condition: Omit<ConditionType, "id">, parentId?: string): string => {
209
+ (condition: Omit<ConditionType<T>, "id">, parentId?: string): string => {
208
210
  const id = generateId();
209
- const newCondition: ConditionType = { ...condition, id };
211
+ const newCondition = { ...condition, id } as ConditionType<T>;
210
212
  if (parentId) {
211
- setItems((prev) => addToParent(prev, parentId, newCondition));
213
+ setItems((prev) => addToParent(prev, parentId, newCondition) as Array<ConditionType<T> | ConditionGroupType<T>>);
212
214
  } else {
213
215
  setItems((prev) => [...prev, newCondition]);
214
216
  }
@@ -220,13 +222,13 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
220
222
  const addConditionGroup = useCallback(
221
223
  (groupOperator: ConditionGroupOperatorType, parentId?: string): string => {
222
224
  const id = generateId();
223
- const newGroup: ConditionGroupType = {
225
+ const newGroup: ConditionGroupType<T> = {
224
226
  id,
225
227
  Operator: groupOperator,
226
228
  Condition: [],
227
229
  };
228
230
  if (parentId) {
229
- setItems((prev) => addToParent(prev, parentId, newGroup));
231
+ setItems((prev) => addToParent(prev, parentId, newGroup) as Array<ConditionType<T> | ConditionGroupType<T>>);
230
232
  } else {
231
233
  setItems((prev) => [...prev, newGroup]);
232
234
  }
@@ -240,14 +242,14 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
240
242
  // ============================================================
241
243
 
242
244
  const updateCondition = useCallback(
243
- (id: string, updates: Partial<Omit<ConditionType, "id">>): void => {
245
+ (id: string, updates: Partial<Omit<ConditionType<T>, "id">>): void => {
244
246
  setItems((prev) =>
245
247
  updateInTree(prev, id, (item) => {
246
248
  if (!isConditionGroup(item)) {
247
249
  return { ...item, ...updates };
248
250
  }
249
251
  return item;
250
- })
252
+ }) as Array<ConditionType<T> | ConditionGroupType<T>>
251
253
  );
252
254
  },
253
255
  []
@@ -261,7 +263,7 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
261
263
  return { ...item, Operator: newOperator };
262
264
  }
263
265
  return item;
264
- })
266
+ }) as Array<ConditionType<T> | ConditionGroupType<T>>
265
267
  );
266
268
  },
267
269
  []
@@ -272,12 +274,12 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
272
274
  // ============================================================
273
275
 
274
276
  const removeCondition = useCallback((id: string): void => {
275
- setItems((prev) => removeFromTree(prev, id));
277
+ setItems((prev) => removeFromTree(prev, id) as Array<ConditionType<T> | ConditionGroupType<T>>);
276
278
  }, []);
277
279
 
278
280
  const getCondition = useCallback(
279
- (id: string): ConditionType | ConditionGroupType | undefined => {
280
- return findById(items, id);
281
+ (id: string): ConditionType<T> | ConditionGroupType<T> | undefined => {
282
+ return findById(items, id) as ConditionType<T> | ConditionGroupType<T> | undefined;
281
283
  },
282
284
  [items]
283
285
  );
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { api, getBdoSchema } from "../../../api";
7
7
  import type { BDOSchemaType, FormOperationType, SubmissionResultType } from "./types";
8
+ import { toError } from "../../../utils/error-handling";
8
9
 
9
10
  // ============================================================
10
11
  // SCHEMA FETCHING
@@ -47,7 +48,7 @@ export async function fetchFormSchemaWithRetry(
47
48
  try {
48
49
  return await fetchFormSchema(source);
49
50
  } catch (error) {
50
- lastError = error as Error;
51
+ lastError = toError(error);
51
52
 
52
53
  if (attempt < maxRetries) {
53
54
  // Wait before retrying (exponential backoff)
@@ -29,6 +29,7 @@ import {
29
29
  import { api } from "../../../api";
30
30
 
31
31
  import { validateCrossField } from "./expressionValidator.utils";
32
+ import { toError } from "../../../utils/error-handling";
32
33
  import {
33
34
  validateFieldOptimized,
34
35
  getFieldDependencies,
@@ -184,11 +185,16 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
184
185
  if (Object.keys(refFields).length > 0) {
185
186
  fetchAllReferenceData(refFields)
186
187
  .then(setReferenceData)
187
- .catch(console.warn);
188
+ .catch((err) => {
189
+ const error = toError(err);
190
+ console.warn("Failed to fetch reference data:", error);
191
+ // Notify via callback but don't block form - reference data is non-critical
192
+ onSchemaErrorRef.current?.(error);
193
+ });
188
194
  }
189
195
  } catch (error) {
190
196
  console.error("Schema processing failed:", error);
191
- onSchemaErrorRef.current?.(error as Error);
197
+ onSchemaErrorRef.current?.(toError(error));
192
198
  }
193
199
  }
194
200
  }, [schema, userRole]); // Removed onSchemaError - using ref instead
@@ -221,6 +227,9 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
221
227
  // Mark as started immediately to prevent duplicate calls
222
228
  draftCreationStartedRef.current = true;
223
229
 
230
+ // Track if effect is still active (for cleanup/race condition handling)
231
+ let isActive = true;
232
+
224
233
  const createInitialDraft = async () => {
225
234
  setIsCreatingDraft(true);
226
235
  setDraftError(null);
@@ -230,6 +239,9 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
230
239
  // Call PATCH /{bdo_id}/draft with empty payload to get draft ID
231
240
  const response = await client.draftInteraction({});
232
241
 
242
+ // Check if effect is still active before setting state
243
+ if (!isActive) return;
244
+
233
245
  // Store the draft ID
234
246
  setDraftId(response._id);
235
247
 
@@ -249,16 +261,27 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
249
261
  });
250
262
  }
251
263
  } catch (error) {
264
+ // Check if effect is still active before setting state
265
+ if (!isActive) return;
266
+
252
267
  console.error("Failed to create initial draft:", error);
253
- setDraftError(error as Error);
268
+ setDraftError(toError(error));
254
269
  // Reset the ref on error so it can be retried
255
270
  draftCreationStartedRef.current = false;
256
271
  } finally {
257
- setIsCreatingDraft(false);
272
+ // Check if effect is still active before setting state
273
+ if (isActive) {
274
+ setIsCreatingDraft(false);
275
+ }
258
276
  }
259
277
  };
260
278
 
261
279
  createInitialDraft();
280
+
281
+ // Cleanup function to handle unmount during async operation
282
+ return () => {
283
+ isActive = false;
284
+ };
262
285
  }, [isInteractiveMode, operation, schemaConfig, enabled, draftId, source]);
263
286
  // Note: rhfForm removed from deps - we use ref pattern to avoid dependency loops
264
287
 
@@ -331,10 +354,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
331
354
  }
332
355
 
333
356
  // Determine if draft should be triggered based on interaction mode
334
- // Interactive mode: Always trigger draft API on blur
357
+ // For update mode, always behave as non-interactive (only trigger for computed deps)
358
+ // Interactive mode (create only): Always trigger draft API on blur
335
359
  // Non-interactive mode: Only trigger for computed field dependencies
336
- const shouldTrigger = isInteractiveMode
337
- ? true // Interactive mode: always trigger
360
+ const shouldTrigger = (isInteractiveMode && operation !== "update")
361
+ ? true // Interactive mode (create only): always trigger
338
362
  : (computedFieldDependencies.length > 0 &&
339
363
  computedFieldDependencies.includes(fieldName as Path<T>));
340
364
 
@@ -653,8 +677,8 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
653
677
  } as any);
654
678
  result = { success: true, data: response };
655
679
  } else {
656
- // Interactive update: POST /{bdo_id}/{id}/draft
657
- const response = await client.draftUpdate(recordId!, cleanedData);
680
+ // Update operation - always use direct update API (non-interactive)
681
+ const response = await client.update(recordId!, cleanedData);
658
682
  result = { success: true, data: response };
659
683
  }
660
684
  } else {
@@ -684,7 +708,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
684
708
  await onSuccess?.(result.data || data, event);
685
709
  } catch (error) {
686
710
  // API error - call onError with Error object
687
- onError?.(error as Error, event);
711
+ onError?.(toError(error), event);
688
712
  } finally {
689
713
  setIsSubmitting(false);
690
714
  }
@@ -940,7 +964,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
940
964
  isCreatingDraft,
941
965
 
942
966
  // Error handling
943
- loadError: loadError as Error | null,
967
+ loadError: loadError ? toError(loadError) : null,
944
968
  hasError,
945
969
 
946
970
  // Schema information
@@ -4,8 +4,11 @@
4
4
  // Core TypeScript interfaces for the kanban board functionality
5
5
  // Following patterns from useTable and useForm
6
6
 
7
- import type { ConditionType, ConditionGroupType, UseFilterReturnType } from "../useFilter";
8
- import type { ConditionGroupOperatorType } from "../../../types/common";
7
+ import type { UseFilterReturnType, UseFilterOptionsType } from "../useFilter";
8
+ import type { ColumnDefinitionType } from "../../../types/common";
9
+
10
+ // Re-export ColumnDefinitionType for backwards compatibility
11
+ export type { ColumnDefinitionType };
9
12
 
10
13
  // ============================================================
11
14
  // CORE DATA STRUCTURES
@@ -95,23 +98,6 @@ export interface KanbanColumnType<T = Record<string, any>> {
95
98
  _modified_at?: Date;
96
99
  }
97
100
 
98
- /**
99
- * Column definition for display and behavior
100
- * Similar to ColumnDefinition in useTable
101
- */
102
- export interface ColumnDefinitionType<T> {
103
- /** Field name from the card type */
104
- fieldId: keyof T;
105
- /** Display label (optional, defaults to fieldId) */
106
- label?: string;
107
- /** Enable sorting for this field */
108
- enableSorting?: boolean;
109
- /** Enable filtering for this field */
110
- enableFiltering?: boolean;
111
- /** Custom transform function (overrides auto-formatting) */
112
- transform?: (value: any, card: T) => React.ReactNode;
113
- }
114
-
115
101
  // ============================================================
116
102
  // DRAG & DROP TYPES
117
103
  // ============================================================
@@ -195,10 +181,8 @@ export interface UseKanbanOptionsType<T> {
195
181
 
196
182
  /** Initial state */
197
183
  initialState?: {
198
- /** Initial filter conditions */
199
- filters?: Array<ConditionType | ConditionGroupType>;
200
- /** Initial filter operator for combining filter conditions */
201
- filterOperator?: ConditionGroupOperatorType;
184
+ /** Initial filter configuration: { conditions, operator } */
185
+ filter?: UseFilterOptionsType;
202
186
  /** Initial search query */
203
187
  search?: string;
204
188
  /** Initial column order */
@@ -7,6 +7,7 @@ import { useState, useMemo, useCallback, useRef, useEffect } from "react";
7
7
  import { useQuery, useMutation, useQueryClient, useQueries, keepPreviousData } from "@tanstack/react-query";
8
8
  import { api } from "../../../api";
9
9
  import type { ListOptionsType, ListResponseType } from "../../../types/common";
10
+ import { toError } from "../../../utils/error-handling";
10
11
  import { useFilter } from "../useFilter";
11
12
 
12
13
  import type {
@@ -41,8 +42,13 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
41
42
 
42
43
  const [search, setSearch] = useState({
43
44
  query: initialState?.search || "",
45
+ debouncedQuery: initialState?.search || "",
44
46
  });
45
47
 
48
+ // Debounce timeout ref for search
49
+ const searchDebounceRef = useRef<NodeJS.Timeout | null>(null);
50
+ const SEARCH_DEBOUNCE_MS = 300;
51
+
46
52
  const [sorting] = useState({
47
53
  field: initialState?.sorting?.field || null,
48
54
  direction: initialState?.sorting?.direction || null,
@@ -81,8 +87,8 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
81
87
  // ============================================================
82
88
 
83
89
  const filter = useFilter({
84
- initialConditions: initialState?.filters,
85
- initialOperator: initialState?.filterOperator || "And",
90
+ conditions: initialState?.filter?.conditions,
91
+ operator: initialState?.filter?.operator || "And",
86
92
  });
87
93
 
88
94
  // Helper to generate API options for a specific column
@@ -139,13 +145,13 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
139
145
  ];
140
146
  }
141
147
 
142
- // Add Search
143
- if (search.query) {
144
- opts.Search = search.query;
148
+ // Add Search (debounced)
149
+ if (search.debouncedQuery) {
150
+ opts.Search = search.debouncedQuery;
145
151
  }
146
152
 
147
153
  return opts;
148
- }, [filter.payload, columnPagination, sorting, search.query]);
154
+ }, [filter.payload, columnPagination, sorting, search.debouncedQuery]);
149
155
 
150
156
  // ============================================================
151
157
  // COLUMN QUERY GENERATION
@@ -180,10 +186,10 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
180
186
  const cardApiOptions = useMemo((): ListOptionsType => {
181
187
  // This is for the GLOBAL count (ignoring column split)
182
188
  const opts: ListOptionsType = {};
183
- if (search.query) opts.Search = search.query;
189
+ if (search.debouncedQuery) opts.Search = search.debouncedQuery;
184
190
  if (filter.payload) opts.Filter = filter.payload;
185
191
  return opts;
186
- }, [search.query, filter.payload]);
192
+ }, [search.debouncedQuery, filter.payload]);
187
193
 
188
194
  const {
189
195
  data: countData,
@@ -196,7 +202,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
196
202
  return await api<KanbanCardType<T>>(source).count(cardApiOptions);
197
203
  } catch (err) {
198
204
  if (onErrorRef.current) {
199
- onErrorRef.current(err as Error);
205
+ onErrorRef.current(toError(err));
200
206
  }
201
207
  throw err;
202
208
  }
@@ -258,7 +264,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
258
264
  if (context?.previousCards && context?.queryKey) {
259
265
  queryClient.setQueryData(context.queryKey, context.previousCards);
260
266
  }
261
- onErrorRef.current?.(error as Error);
267
+ onErrorRef.current?.(toError(error));
262
268
  },
263
269
  onSettled: (_data, _error, variables) => {
264
270
  const columnId = variables.columnId;
@@ -280,7 +286,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
280
286
  onCardUpdateRef.current?.({ _id: result.id, ...result.updates } as any);
281
287
  },
282
288
  onError: (error, _variables, _context) => {
283
- onErrorRef.current?.(error as Error);
289
+ onErrorRef.current?.(toError(error));
284
290
  },
285
291
  onSettled: () => {
286
292
  queryClient.invalidateQueries({ queryKey: ["kanban-cards", source] });
@@ -300,7 +306,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
300
306
  onCardDeleteRef.current?.(id);
301
307
  },
302
308
  onError: (error, _id, _context) => {
303
- onErrorRef.current?.(error as Error);
309
+ onErrorRef.current?.(toError(error));
304
310
  },
305
311
  onSettled: () => {
306
312
  queryClient.invalidateQueries({ queryKey: ["kanban-cards", source] });
@@ -374,7 +380,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
374
380
  if (context?.previousToData && context?.toQueryKey) {
375
381
  queryClient.setQueryData(context.toQueryKey, context.previousToData);
376
382
  }
377
- onErrorRef.current?.(error as Error);
383
+ onErrorRef.current?.(toError(error));
378
384
  },
379
385
  onSettled: (_data, _error, variables) => {
380
386
  const fromOpts = getColumnApiOptions(variables.fromColumnId);
@@ -401,7 +407,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
401
407
  },
402
408
  onSuccess: () => {},
403
409
  onError: (error, _variables, _context) => {
404
- onErrorRef.current?.(error as Error);
410
+ onErrorRef.current?.(toError(error));
405
411
  },
406
412
  onSettled: (_data, _error, variables) => {
407
413
  const opts = getColumnApiOptions(variables.columnId);
@@ -513,11 +519,32 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
513
519
  // ============================================================
514
520
 
515
521
  const setSearchQuery = useCallback((value: string) => {
516
- setSearch({ query: value });
522
+ // Validate search query length to prevent DoS
523
+ if (value.length > 255) {
524
+ console.warn("Search query exceeds maximum length of 255 characters");
525
+ return;
526
+ }
527
+
528
+ // Update immediate value for UI
529
+ setSearch((prev) => ({ ...prev, query: value }));
530
+
531
+ // Clear existing debounce timeout
532
+ if (searchDebounceRef.current) {
533
+ clearTimeout(searchDebounceRef.current);
534
+ }
535
+
536
+ // Debounce the actual API query update
537
+ searchDebounceRef.current = setTimeout(() => {
538
+ setSearch((prev) => ({ ...prev, debouncedQuery: value }));
539
+ }, SEARCH_DEBOUNCE_MS);
517
540
  }, []);
518
541
 
519
542
  const clearSearch = useCallback(() => {
520
- setSearch({ query: "" });
543
+ // Clear debounce timeout
544
+ if (searchDebounceRef.current) {
545
+ clearTimeout(searchDebounceRef.current);
546
+ }
547
+ setSearch({ query: "", debouncedQuery: "" });
521
548
  }, []);
522
549
 
523
550
  const totalCards = countData?.Count || 0;
@@ -555,10 +582,19 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
555
582
 
556
583
  useEffect(() => {
557
584
  if (error && onErrorRef.current) {
558
- onErrorRef.current(error as Error);
585
+ onErrorRef.current(toError(error));
559
586
  }
560
587
  }, [error]);
561
588
 
589
+ // Cleanup debounce timeout on unmount
590
+ useEffect(() => {
591
+ return () => {
592
+ if (searchDebounceRef.current) {
593
+ clearTimeout(searchDebounceRef.current);
594
+ }
595
+ };
596
+ }, []);
597
+
562
598
  // ============================================================
563
599
  // RETURN OBJECT
564
600
  // ============================================================
@@ -574,7 +610,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
574
610
  isUpdating,
575
611
 
576
612
  // Error Handling
577
- error: error as Error | null,
613
+ error: error ? toError(error) : null,
578
614
 
579
615
  // Card Operations (Flat Access)
580
616
  createCard: createCardMutation.mutateAsync,
@@ -1,46 +1,40 @@
1
- import type { ListResponseType, ConditionGroupOperatorType } from "../../../types/common";
2
- import type { ConditionType, ConditionGroupType, UseFilterReturnType } from "../useFilter";
1
+ import type { ListResponseType, SortType, ColumnDefinitionType } from "../../../types/common";
2
+
3
+ // Re-export ColumnDefinitionType for backwards compatibility
4
+ export type { ColumnDefinitionType };
5
+ import type { UseFilterReturnType, UseFilterOptionsType } from "../useFilter";
3
6
 
4
7
  // ============================================================
5
- // TYPE DEFINITIONS
8
+ // STATE TYPE DEFINITIONS
6
9
  // ============================================================
7
10
 
8
- export interface ColumnDefinitionType<T> {
9
- /** Field name from the data type */
10
- fieldId: keyof T;
11
- /** Display label (optional, defaults to fieldId) */
12
- label?: string;
13
- /** Enable sorting for this column */
14
- enableSorting?: boolean;
15
- /** Enable filtering for this column */
16
- enableFiltering?: boolean;
17
- /** Custom transform function (overrides auto-formatting) */
18
- transform?: (value: any, row: T) => React.ReactNode;
11
+ /**
12
+ * Pagination state type
13
+ */
14
+ export interface PaginationStateType {
15
+ /** Page number (1-indexed) */
16
+ pageNo: number;
17
+ /** Number of items per page */
18
+ pageSize: number;
19
19
  }
20
20
 
21
+ // ============================================================
22
+ // TYPE DEFINITIONS
23
+ // ============================================================
24
+
21
25
  export interface UseTableOptionsType<T> {
22
26
  /** Data source identifier */
23
27
  source: string;
24
28
  /** Column configurations */
25
29
  columns: ColumnDefinitionType<T>[];
26
- /** Enable sorting functionality */
27
- enableSorting?: boolean;
28
- /** Enable filtering functionality */
29
- enableFiltering?: boolean;
30
- /** Enable pagination */
31
- enablePagination?: boolean;
32
30
  /** Initial state */
33
31
  initialState?: {
34
- pagination?: {
35
- pageNo: number;
36
- pageSize: number;
37
- };
38
- sorting?: {
39
- field: keyof T;
40
- direction: "asc" | "desc";
41
- };
42
- filters?: Array<ConditionType | ConditionGroupType>;
43
- filterOperator?: ConditionGroupOperatorType;
32
+ /** Sort configuration: [{ "fieldName": "ASC" }] */
33
+ sort?: SortType;
34
+ /** Pagination state: { pageNo, pageSize } */
35
+ pagination?: PaginationStateType;
36
+ /** Filter state: { conditions, operator } */
37
+ filter?: UseFilterOptionsType<T>;
44
38
  };
45
39
  /** Error callback */
46
40
  onError?: (error: Error) => void;
@@ -77,11 +71,11 @@ export interface UseTableReturnType<T> {
77
71
  };
78
72
 
79
73
  // Filter (Simplified chainable API)
80
- filter: UseFilterReturnType;
74
+ filter: UseFilterReturnType<T>;
81
75
 
82
76
  // Pagination (Flat Access)
83
77
  pagination: {
84
- currentPage: number;
78
+ pageNo: number;
85
79
  pageSize: number;
86
80
  totalPages: number;
87
81
  totalItems: number;