@ram_28/kf-ai-sdk 2.0.27 → 2.0.29

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.
@@ -25,6 +25,26 @@ import type { ListOptionsType } from "@ram_28/kf-ai-sdk/api/types";
25
25
  ```typescript
26
26
  const product = useMemo(() => new AdminProduct(), []);
27
27
  ```
28
+ 5. **`Filter` must be `ConditionGroupType`** — Always wrap conditions in `{ Operator: "And", Condition: [...] }`. Never pass a flat condition:
29
+ ```typescript
30
+ // ❌ WRONG — flat ConditionType causes TS2322
31
+ await product.list({ Filter: { Operator: "EQ", LHSField: "Category", RHSValue: "Electronics" } });
32
+ // ✅ CORRECT — ConditionGroupType wrapper
33
+ await product.list({ Filter: { Operator: "And", Condition: [
34
+ { Operator: "EQ", LHSField: "Category", RHSValue: "Electronics", RHSType: "Constant" }
35
+ ] } });
36
+ ```
37
+ 6. **Use `PageSize` and `Page`, not `Take` or `Limit`** — `ListOptionsType` uses `PageSize` (number of items per page) and `Page` (1-indexed page number).
38
+ 7. **Never spread ItemType proxies.** `list()`, `get()`, and `create()` return proxied `ItemType` objects. Spreading `{ ...item, extraProp }` copies enumerable keys but destroys the proxy — `.get()` and `.set()` stop working. Instead, wrap in a container object:
39
+ ```typescript
40
+ // ❌ WRONG — proxy is destroyed, .get() fails at runtime
41
+ const enriched = items.map(item => ({ ...item, extra: fetchedData }));
42
+ enriched[0].Title.get(); // TypeError: get is not a function
43
+
44
+ // ✅ CORRECT — proxy preserved in container
45
+ const enriched = items.map(item => ({ entry: item, extra: fetchedData }));
46
+ enriched[0].entry.Title.get(); // works
47
+ ```
28
48
 
29
49
  ## Quick Start
30
50
 
@@ -82,7 +102,7 @@ const filtered = await product.count({
82
102
  Filter: {
83
103
  Operator: "And",
84
104
  Condition: [
85
- { Operator: "eq", LHSField: "Category", RHSValue: "Electronics" },
105
+ { Operator: "EQ", LHSField: "Category", RHSValue: "Electronics", RHSType: "Constant" },
86
106
  ],
87
107
  },
88
108
  });
@@ -125,8 +145,8 @@ const items = await product.list({
125
145
  Filter: {
126
146
  Operator: "And",
127
147
  Condition: [
128
- { Operator: "eq", LHSField: "Category", RHSValue: "Electronics" },
129
- { Operator: "gte", LHSField: "Price", RHSValue: 10 },
148
+ { Operator: "EQ", LHSField: "Category", RHSValue: "Electronics", RHSType: "Constant" },
149
+ { Operator: "GTE", LHSField: "Price", RHSValue: 10, RHSType: "Constant" },
130
150
  ],
131
151
  },
132
152
  Sort: [{ Price: "DESC" }],
@@ -140,7 +140,7 @@ interface ConditionGroupType {
140
140
  }
141
141
 
142
142
  interface ConditionType {
143
- Operator: string; // "eq", "neq", "gt", "gte", "lt", "lte", "contains", "startswith", etc.
143
+ Operator: string; // "EQ", "NE", "GT", "GTE", "LT", "LTE", "Contains", "StartsWith", "IN", "NIN", "Empty", "NotEmpty"
144
144
  LHSField: string; // field name
145
145
  RHSValue: any; // comparison value
146
146
  RHSType?: string; // defaults to "Constant"
@@ -6,7 +6,7 @@
6
6
  import { useState, useMemo } from "react";
7
7
  import { useBDOTable } from "@ram_28/kf-ai-sdk/table";
8
8
  import type { UseBDOTableReturnType } from "@ram_28/kf-ai-sdk/table/types";
9
- import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
9
+ import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/table";
10
10
  import { BuyerProduct } from "@/bdo/buyer/Product";
11
11
 
12
12
  export default function FilteredProductTable() {
@@ -6,7 +6,7 @@
6
6
  import { useState, useMemo } from "react";
7
7
  import { useActivityTable, ActivityTableStatus } from "@ram_28/kf-ai-sdk/workflow";
8
8
  import type { UseActivityTableReturnType } from "@ram_28/kf-ai-sdk/workflow";
9
- import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
9
+ import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/table";
10
10
  import { EmployeeInputActivity } from "@/workflow/leave";
11
11
 
12
12
  export default function FilteredActivityTable() {
@@ -64,8 +64,11 @@ This applies to ALL field types: `StringField.get()` → `string | undefined`, `
64
64
  2. **`.get()` returns `T | undefined`** — Always null-guard before passing to `new Date()`, `format()`, template literals, or typed function parameters. See section above.
65
65
  3. **Don't confuse `StringField` (class) with `StringFieldType` (type alias)** — Different modules.
66
66
  4. **`fetchOptions()` requires a parent BDO** — Standalone fields will throw.
67
- 5. **SelectField meta `Type` is `"String"`** — `Constraint.Enum` differentiates it.
67
+ 5. **SelectField meta `Type` is `"String"`** — `Constraint.Enum` differentiates it. Only `SelectField` has the `.options` getter. `StringField` (even with an enum constraint) does NOT have `.options` — you must hardcode the enum values from the BDO file.
68
68
  6. **Always use pre-built components for File/Image** — `<FileUpload>`, `<ImageUpload>`, `<FilePreview>`, `<ImageThumbnail>`.
69
+ 7. **`<ImageUpload>` / `<FileUpload>` ONLY work with `ImageField` / `FileField`** — Never use them for `TextField` or `StringField`, even if the field name contains "image" or "file". A `TextField` named `image_urls` is still a text field — render it with `<Textarea>` or `<Input>`, not `<ImageUpload>`. Determine the component from the **field class** in the BDO file, not the field name.
70
+ 8. **`<ImageUpload>` / `<FileUpload>` `field` prop MUST be `item.Field`, NOT `bdo.Field`** — The `upload()` and `deleteAttachment()` methods live on the form item accessor (created by the Item proxy), not on the BDO field class. Passing `bdo.Field` causes silent upload failures.
71
+ 9. **BooleanField: use `watch()` + `setValue()`, never `register()`** — `<Switch>` and `<Checkbox>` components don't fire native change events. Using `register()` means the value never updates on toggle.
69
72
 
70
73
  ## Quick Start
71
74
 
@@ -242,3 +242,4 @@ item.StartDate.readOnly; // is readonly?
242
242
  - **Don't call `activity.update()` or `activity.complete()` manually.** `handleSubmit` handles the API calls. Calling them yourself will double-submit.
243
243
  - **Don't render the form before `isLoading` is false.** The activity data and metadata are being fetched. Guard with `if (isLoading) return <Loading />`.
244
244
  - **Don't forget `activity_instance_id` is required.** This is the activity instance ID (from `workflow.start()` or `getInProgressList()`), not a BDO record ID.
245
+ - **Don't pass a single options object.** `useActivityForm` takes 2 arguments: `useActivityForm(activity, { activity_instance_id })`. This is different from `useActivityTable` which takes a single object `useActivityTable({ activity, status })`. Passing `useActivityForm({ activity, activity_instance_id })` causes a type error.
@@ -161,14 +161,14 @@ Sorting is controlled through the `sort` object. Column headers typically use `t
161
161
  The table includes an integrated [`useFilter`](../useFilter/README.md) instance at `table.filter`. Use it to build filter conditions that narrow down the table results:
162
162
 
163
163
  ```tsx
164
- import { ConditionOperator } from "@ram_28/kf-ai-sdk/table";
164
+ import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/table";
165
165
 
166
166
  // Filter by leave type
167
167
  table.filter.addCondition({
168
168
  Operator: ConditionOperator.EQ,
169
169
  LHSField: activity.LeaveType.id,
170
170
  RHSValue: "PTO",
171
- RHSType: "Constant",
171
+ RHSType: RHSType.Constant,
172
172
  });
173
173
 
174
174
  // Date range filter
@@ -176,7 +176,7 @@ table.filter.addCondition({
176
176
  Operator: ConditionOperator.GTE,
177
177
  LHSField: activity.StartDate.id,
178
178
  RHSValue: "2026-01-01",
179
- RHSType: "Constant",
179
+ RHSType: RHSType.Constant,
180
180
  });
181
181
 
182
182
  // Clear all filters
@@ -261,3 +261,5 @@ This is the standard pattern after any mutation (complete, update, save) that sh
261
261
  - **Don't use 0-indexed pagination.** Pages start at 1. Passing `pageNo: 0` will produce incorrect results.
262
262
  - **Don't forget to guard on `isLoading` before rendering rows.** While loading, `rows` is an empty array. Guard with `if (table.isLoading) return <Loading />` to avoid rendering an empty table.
263
263
  - **Don't hardcode field names as strings.** Use the activity field IDs (`activity.StartDate.id`) instead of raw strings (`"StartDate"`). This keeps your code type-safe and resilient to field renames.
264
+ - **Don't use `table.data`** — the property is `table.rows`, same as `useBDOTable`.
265
+ - **`initialState.sort` format is `[{ fieldName: "ASC" }]`** — NOT `{ field, direction }`. Write `sort: [{ StartDate: "DESC" }]`, not `sort: [{ field: "StartDate", direction: "desc" }]`.
@@ -170,6 +170,32 @@ item.Title.readOnly; // is readonly?
170
170
  - **Don't forget `useMemo` on the BDO.** `new BdoClass()` must be wrapped in `useMemo(() => ..., [])`. Re-creating the instance on every render breaks the hook.
171
171
  - **Don't set date `defaultValues` to empty string.** Use `undefined` instead. Empty strings cause type validation errors for Date and DateTime fields.
172
172
  - **Don't mix `register()` and `watch()`+`setValue()` for the same field.** Pick one approach per field. `register()` for native inputs, `watch()`+`setValue()` for custom components.
173
- - **Don't use `register()` for select, checkbox, or reference components.** They don't fire native change events. Use `watch()` + `setValue()`.
173
+ - **Don't use `register()` for select, checkbox, switch, or reference components.** They don't fire native change events. Use `watch()` + `setValue()`.
174
174
  - **Don't call `bdo.create()` or `bdo.update()` manually.** `handleSubmit` handles the API call. Calling them yourself will double-submit.
175
+ - **`handleSubmit` is curried — pass it directly to `onSubmit`, don't call it inside a handler.** It takes `(onSuccess, onError)` and returns an event handler. Write `<form onSubmit={handleSubmit(onSuccess, onError)}>`. NEVER write `<form onSubmit={(e) => handleSubmit(data)}>` or `await handleSubmit(data)` — this passes the wrong argument and the API call never fires.
176
+ ```tsx
177
+ // ❌ WRONG — handleSubmit receives FormEvent, not form data. API never fires.
178
+ const onSubmit = async (data) => { await handleSubmit(data); };
179
+ <form onSubmit={onSubmit}>
180
+
181
+ // ✅ CORRECT — handleSubmit wraps your callback, validates, calls API, then invokes onSuccess
182
+ <form onSubmit={handleSubmit(
183
+ (result) => { toast.success("Created"); navigate("/products"); },
184
+ (error) => { toast.error("Failed"); }
185
+ )}>
186
+ ```
175
187
  - **Don't render the form before `isLoading` is false.** In create mode, the draft is being allocated. In update mode, the record is being fetched. Guard with `if (isLoading) return <Loading />`.
188
+ - **There is no `operation` field.** Create vs. update mode is determined solely by `recordId`: absent = create, present = update. Never pass `operation: "create"` or `operation: "update"`.
189
+ - **Guard `loadError` in edit forms.** After the `isLoading` check, add `if (loadError) return <ErrorMessage />` before rendering the form. Without this, `item` may be undefined in update mode.
190
+ ```tsx
191
+ if (isLoading) return <Loading />;
192
+ if (loadError) return <p>Error: {loadError.message}</p>;
193
+ ```
194
+ - **Don't pass `operation` even though the SDK type supports it.** The SDK has `UseBDOFormAutoOptionsType` that auto-infers create/update from `recordId`. Passing `operation: "update"` requires `recordId: string` (not `string | undefined`), causing TS errors with `useParams()`. Omit `operation` entirely:
195
+ ```tsx
196
+ // ❌ WRONG — TS2345 when id is string | undefined from useParams
197
+ useBDOForm({ bdo, recordId: id, operation: "update" })
198
+
199
+ // ✅ CORRECT — auto mode handles string | undefined
200
+ useBDOForm({ bdo, recordId: id })
201
+ ```
@@ -141,14 +141,14 @@ Sorting is controlled through the `sort` object. Column headers typically use `t
141
141
  The table includes an integrated [`useFilter`](../useFilter/README.md) instance at `table.filter`. Use it to build filter conditions that narrow down the table results:
142
142
 
143
143
  ```tsx
144
- import { ConditionOperator } from "@ram_28/kf-ai-sdk/table";
144
+ import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/table";
145
145
 
146
146
  // Exact match
147
147
  table.filter.addCondition({
148
148
  Operator: ConditionOperator.EQ,
149
149
  LHSField: product.Category.id,
150
150
  RHSValue: "Electronics",
151
- RHSType: "Constant",
151
+ RHSType: RHSType.Constant,
152
152
  });
153
153
 
154
154
  // Range filter
@@ -156,6 +156,7 @@ table.filter.addCondition({
156
156
  Operator: ConditionOperator.GTE,
157
157
  LHSField: product.Price.id,
158
158
  RHSValue: 100,
159
+ RHSType: RHSType.Constant,
159
160
  });
160
161
 
161
162
  // Clear all filters
@@ -236,7 +237,17 @@ This is the standard pattern after any mutation (create, update, delete) that sh
236
237
  ## Common Mistakes
237
238
 
238
239
  - **Don't forget `useMemo` on the BDO.** `new BdoClass()` must be wrapped in `useMemo(() => ..., [])`. Re-creating the instance on every render causes infinite refetching.
240
+ - **Don't use `table.data`** — the property is `table.rows`. Other table libraries use `data`, but this SDK uses `rows`.
241
+ - **Don't use `table.setSearch()`** — the correct API is `table.search.set(field, query)` with two arguments: field ID and query string.
239
242
  - **Don't access row fields directly.** `row.Title` is an accessor object, not the value. Use `row.Title.get()` to read the value.
240
243
  - **Don't use 0-indexed pagination.** Pages start at 1. Passing `pageNo: 0` will produce incorrect results.
241
244
  - **Don't forget to guard on `isLoading` before rendering rows.** While loading, `rows` is an empty array. Guard with `if (table.isLoading) return <Loading />` to avoid rendering an empty table.
242
245
  - **Don't hardcode field names as strings.** Use the BDO field IDs (`product.Title.id`) instead of raw strings (`"Title"`). This keeps your code type-safe and resilient to field renames.
246
+ - **`initialState.sort` format is `[{ fieldName: "ASC" }]`** — a single-key object, NOT `{ field, direction }`. Other libraries use `{ field: "Title", direction: "asc" }` but this SDK uses `[{ Title: "ASC" }]`. Note: the direction is uppercase `"ASC"` or `"DESC"`.
247
+ ```tsx
248
+ // ❌ WRONG — { field, direction } format from other libraries
249
+ initialState: { sort: [{ field: product.Title.id, direction: "desc" }] }
250
+ // ✅ CORRECT — single-key object with uppercase direction
251
+ initialState: { sort: [{ Title: "DESC" }] }
252
+ ```
253
+ - **Don't pass `bdo.field` where `bdo.field.id` is expected.** `bdo.Title` is a `StringField` object, not a string. Use `bdo.Title.id` to get the field ID string. Passing the field object causes TS2322 (`StringField` not assignable to `string`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ram_28/kf-ai-sdk",
3
- "version": "2.0.27",
3
+ "version": "2.0.29",
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",