@ram_28/kf-ai-sdk 2.0.12 → 2.0.14
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/dist/api/client.d.ts.map +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +2 -2
- package/dist/attachment-constants-B5jlqoKI.cjs +1 -0
- package/dist/attachment-constants-C2UHWxmp.js +63 -0
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/bdo/core/types.d.ts +4 -0
- package/dist/bdo/core/types.d.ts.map +1 -1
- package/dist/bdo/fields/NumberField.d.ts.map +1 -1
- package/dist/bdo/fields/ReferenceField.d.ts +3 -2
- package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
- package/dist/bdo/fields/SelectField.d.ts +1 -1
- package/dist/bdo/fields/SelectField.d.ts.map +1 -1
- package/dist/bdo/fields/UserField.d.ts +5 -0
- package/dist/bdo/fields/UserField.d.ts.map +1 -1
- package/dist/bdo.cjs +1 -1
- package/dist/bdo.mjs +107 -153
- package/dist/client-DnO2KKrw.cjs +1 -0
- package/dist/{client-CMERmrC-.js → client-iQTqFDNI.js} +34 -30
- package/dist/components/hooks/useForm/createItemProxy.d.ts +4 -0
- package/dist/components/hooks/useForm/createItemProxy.d.ts.map +1 -1
- package/dist/components/hooks/useForm/createResolver.d.ts.map +1 -1
- package/dist/components/hooks/useForm/useForm.d.ts +1 -0
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +368 -203
- package/dist/{metadata-BfJtHz84.cjs → metadata-DgLSJkF5.cjs} +1 -1
- package/dist/{metadata-CwAo6a8e.js → metadata-DpfI3zRN.js} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +1 -1
- package/dist/workflow/types.d.ts +3 -2
- package/dist/workflow/types.d.ts.map +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.d.ts +0 -2
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.mjs +204 -274
- package/dist/workflow.types.d.ts +0 -1
- package/dist/workflow.types.d.ts.map +1 -1
- package/docs/api.md +45 -253
- package/docs/bdo.md +130 -711
- package/docs/useAuth.md +42 -104
- package/docs/useFilter.md +117 -1591
- package/docs/useForm.md +266 -861
- package/docs/useTable.md +255 -1096
- package/docs/workflow.md +10 -155
- package/package.json +1 -1
- package/sdk/api/client.ts +18 -4
- package/sdk/bdo/core/types.ts +1 -0
- package/sdk/bdo/fields/NumberField.ts +2 -1
- package/sdk/bdo/fields/ReferenceField.ts +4 -3
- package/sdk/bdo/fields/SelectField.ts +2 -2
- package/sdk/bdo/fields/UserField.ts +14 -0
- package/sdk/components/hooks/useForm/createItemProxy.ts +221 -4
- package/sdk/components/hooks/useForm/createResolver.ts +16 -1
- package/sdk/components/hooks/useForm/useForm.ts +151 -50
- package/sdk/workflow/types.ts +3 -2
- package/sdk/workflow.ts +0 -7
- package/sdk/workflow.types.ts +0 -7
- package/dist/client-BnVxSHAm.cjs +0 -1
- package/dist/workflow/components/useActivityTable/index.d.ts +0 -4
- package/dist/workflow/components/useActivityTable/index.d.ts.map +0 -1
- package/dist/workflow/components/useActivityTable/types.d.ts +0 -53
- package/dist/workflow/components/useActivityTable/types.d.ts.map +0 -1
- package/dist/workflow/components/useActivityTable/useActivityTable.d.ts +0 -4
- package/dist/workflow/components/useActivityTable/useActivityTable.d.ts.map +0 -1
- package/sdk/workflow/components/useActivityTable/index.ts +0 -8
- package/sdk/workflow/components/useActivityTable/types.ts +0 -67
- package/sdk/workflow/components/useActivityTable/useActivityTable.ts +0 -145
package/docs/bdo.md
CHANGED
|
@@ -5,801 +5,220 @@ Type-safe, role-based data access layer for business objects.
|
|
|
5
5
|
## Imports
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
// BDO class (from
|
|
9
|
-
import { AdminProduct } from "
|
|
10
|
-
|
|
11
|
-
// Field types (from your generated code)
|
|
12
|
-
import type {
|
|
13
|
-
AdminProductEditableFieldType,
|
|
14
|
-
AdminProductReadonlyFieldType,
|
|
15
|
-
AdminProductFieldType,
|
|
16
|
-
} from "../bdo/admin/Product";
|
|
8
|
+
// BDO class (from generated code)
|
|
9
|
+
import { AdminProduct } from "@/bdo/admin/Product";
|
|
10
|
+
import type { AdminProductEditableFieldType, AdminProductFieldType } from "@/bdo/admin/Product";
|
|
17
11
|
|
|
18
12
|
// SDK types
|
|
19
|
-
import type {
|
|
20
|
-
|
|
21
|
-
BdoMetaType,
|
|
22
|
-
ValidationResultType,
|
|
23
|
-
SelectOptionType,
|
|
24
|
-
EditableFieldAccessorType,
|
|
25
|
-
ReadonlyFieldAccessorType,
|
|
26
|
-
BaseFieldMetaType,
|
|
27
|
-
} from "@ram_28/kf-ai-sdk/bdo/types";
|
|
28
|
-
|
|
29
|
-
// System fields
|
|
30
|
-
import type { SystemFieldsType } from "@ram_28/kf-ai-sdk/bdo/types";
|
|
31
|
-
|
|
32
|
-
// API types (for method params/responses)
|
|
33
|
-
import type {
|
|
34
|
-
ListOptionsType,
|
|
35
|
-
CreateUpdateResponseType,
|
|
36
|
-
DeleteResponseType,
|
|
37
|
-
DraftResponseType,
|
|
38
|
-
MetricOptionsType,
|
|
39
|
-
MetricResponseType,
|
|
40
|
-
PivotOptionsType,
|
|
41
|
-
PivotResponseType,
|
|
42
|
-
} from "@ram_28/kf-ai-sdk/api/types";
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## BDO Meta
|
|
46
|
-
|
|
47
|
-
Every BDO has a `meta` property identifying the business object.
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
const product = new AdminProduct();
|
|
51
|
-
|
|
52
|
-
product.meta._id; // "BDO_Product"
|
|
53
|
-
product.meta.name; // "Product"
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Use `meta._id` anywhere a source identifier is needed (e.g., `useTable`, `useFilter`).
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## Field Definitions
|
|
61
|
-
|
|
62
|
-
Fields are readonly properties on the BDO class, constructed with raw backend meta JSON.
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
// In the generated BDO class
|
|
66
|
-
readonly Title = new StringField({
|
|
67
|
-
_id: "Title",
|
|
68
|
-
Name: "Product Title",
|
|
69
|
-
Type: "String",
|
|
70
|
-
Constraint: { Required: true, Length: 255 },
|
|
71
|
-
});
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### BaseField Getters
|
|
75
|
-
|
|
76
|
-
Every field exposes these getters:
|
|
77
|
-
|
|
78
|
-
| Getter | Type | Source |
|
|
79
|
-
|--------|------|--------|
|
|
80
|
-
| `field.id` | `string` | `_meta._id` |
|
|
81
|
-
| `field.label` | `string` | `_meta.Name` |
|
|
82
|
-
| `field.readOnly` | `boolean` | `_meta.ReadOnly` |
|
|
83
|
-
| `field.required` | `boolean` | `_meta.Constraint.Required` or `_meta.Required` |
|
|
84
|
-
| `field.defaultValue` | `unknown` | `_meta.DefaultValue` or `_meta.Constraint.DefaultValue` |
|
|
85
|
-
| `field.primaryKey` | `boolean` | `_meta.Constraint.PrimaryKey` |
|
|
86
|
-
| `field.meta` | `BaseFieldMetaType` | Full raw backend meta object |
|
|
87
|
-
|
|
88
|
-
### Usage
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
const product = new AdminProduct();
|
|
92
|
-
|
|
93
|
-
// react-hook-form register
|
|
94
|
-
register(product.Title.id);
|
|
95
|
-
|
|
96
|
-
// UI labels
|
|
97
|
-
<label>{product.Title.label}</label>
|
|
98
|
-
|
|
99
|
-
// Required indicator
|
|
100
|
-
{product.Title.required && <span>*</span>}
|
|
101
|
-
|
|
102
|
-
// Filter by field
|
|
103
|
-
const items = await product.list({
|
|
104
|
-
Filter: {
|
|
105
|
-
Operator: "And",
|
|
106
|
-
Condition: [{
|
|
107
|
-
LHSField: product.Price.id,
|
|
108
|
-
Operator: "GT",
|
|
109
|
-
RHSValue: 50,
|
|
110
|
-
RHSType: "Constant",
|
|
111
|
-
}],
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Methods
|
|
119
|
-
|
|
120
|
-
All `BaseBdo` methods are `protected`. Role BDOs selectively re-expose them as `public` based on permissions. For full request/response type definitions, see [api.md](api.md).
|
|
121
|
-
|
|
122
|
-
### get
|
|
123
|
-
|
|
124
|
-
Fetches a single record by ID.
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
async get(id: string): Promise<ItemType<TEditable, TReadonly>>
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Returns an [ItemType](#itemtype--runtime-accessor-pattern) with field accessors.
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
const item = await product.get("prod_abc123");
|
|
134
|
-
|
|
135
|
-
console.log(item._id); // "prod_abc123"
|
|
136
|
-
console.log(item.Title.get()); // "Wireless Headphones"
|
|
137
|
-
console.log(item.Title.label); // "Product Title"
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
### list
|
|
143
|
-
|
|
144
|
-
Fetches paginated records with optional filtering, sorting, and pagination.
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
async list(options?: ListOptionsType): Promise<ItemType<TEditable, TReadonly>[]>
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Returns an array of [ItemType](#itemtype--runtime-accessor-pattern).
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
const items = await product.list({
|
|
154
|
-
Filter: {
|
|
155
|
-
Operator: "And",
|
|
156
|
-
Condition: [{
|
|
157
|
-
LHSField: product.Category.id,
|
|
158
|
-
Operator: "EQ",
|
|
159
|
-
RHSValue: "Electronics",
|
|
160
|
-
RHSType: "Constant",
|
|
161
|
-
}],
|
|
162
|
-
},
|
|
163
|
-
Sort: [{ [product.Price.id]: "ASC" }],
|
|
164
|
-
Page: 1,
|
|
165
|
-
PageSize: 20,
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
items.forEach((item) => {
|
|
169
|
-
console.log(item.Title.get(), item.Price.get());
|
|
170
|
-
});
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
### count
|
|
176
|
-
|
|
177
|
-
Returns the count of records matching filter criteria.
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
async count(options?: ListOptionsType): Promise<number>
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Uses the same `ListOptionsType` for filtering. Returns the count directly as a `number`.
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
const total = await product.count();
|
|
187
|
-
console.log(`Total products: ${total}`);
|
|
188
|
-
|
|
189
|
-
// With filter
|
|
190
|
-
const lowStock = await product.count({
|
|
191
|
-
Filter: {
|
|
192
|
-
Operator: "And",
|
|
193
|
-
Condition: [{
|
|
194
|
-
LHSField: product.Stock.id,
|
|
195
|
-
Operator: "LT",
|
|
196
|
-
RHSValue: 10,
|
|
197
|
-
RHSType: "Constant",
|
|
198
|
-
}],
|
|
199
|
-
},
|
|
200
|
-
});
|
|
13
|
+
import type { ItemType, SystemFieldsType } from "@ram_28/kf-ai-sdk/bdo/types";
|
|
14
|
+
import type { ListOptionsType, CreateUpdateResponseType, DeleteResponseType } from "@ram_28/kf-ai-sdk/api/types";
|
|
201
15
|
```
|
|
202
16
|
|
|
203
17
|
---
|
|
204
18
|
|
|
205
|
-
|
|
19
|
+
## Common Mistakes (READ FIRST)
|
|
206
20
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
async create(data: Partial<TEditable>): Promise<ItemType<TEditable, TReadonly>>
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Returns an [ItemType](#itemtype--runtime-accessor-pattern) with the new `_id` from the API response and the input data as field accessors.
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
const newItem = await product.create({
|
|
217
|
-
Title: "Wireless Headphones",
|
|
218
|
-
Price: 99.99,
|
|
219
|
-
Category: "Electronics",
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
console.log(newItem._id); // New record ID
|
|
223
|
-
console.log(newItem.Title.get()); // "Wireless Headphones"
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
### update
|
|
229
|
-
|
|
230
|
-
Updates an existing record.
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
async update(id: string, data: Partial<TEditable>): Promise<CreateUpdateResponseType>
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
Returns `{ _id: string }`.
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
const result = await product.update("prod_abc123", {
|
|
240
|
-
Price: 79.99,
|
|
241
|
-
Stock: 45,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
console.log("Updated:", result._id);
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
### delete
|
|
250
|
-
|
|
251
|
-
Deletes a record by ID.
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
async delete(id: string): Promise<DeleteResponseType>
|
|
255
|
-
```
|
|
21
|
+
### 1. Guessing field names instead of reading BDO files
|
|
256
22
|
|
|
257
|
-
|
|
23
|
+
ALWAYS read `src/bdo/{role}/*.ts` BEFORE writing page code. Use EXACT field names from the class.
|
|
258
24
|
|
|
259
25
|
```typescript
|
|
260
|
-
|
|
26
|
+
// ❌ WRONG — guessing
|
|
27
|
+
bdo.productName bdo.isActive bdo.unitPrice
|
|
261
28
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
29
|
+
// ✅ CORRECT — exact snake_case from BDO file
|
|
30
|
+
bdo.product_name bdo.is_active bdo.unit_price
|
|
265
31
|
```
|
|
266
32
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
### draft
|
|
270
|
-
|
|
271
|
-
Previews computed field values for a new record without saving.
|
|
33
|
+
### 2. Rendering ItemType fields directly in JSX without `.get()`
|
|
272
34
|
|
|
273
|
-
|
|
274
|
-
async draft(data: Partial<TEditable>): Promise<DraftResponseType>
|
|
275
|
-
```
|
|
35
|
+
`bdo.list()`, `bdo.get()`, `bdo.create()` return `ItemType` — each field is an accessor object, NOT a value.
|
|
276
36
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
Price: 100,
|
|
282
|
-
DiscountPercent: 15,
|
|
283
|
-
});
|
|
37
|
+
```tsx
|
|
38
|
+
// ❌ WRONG — renders [object Object]
|
|
39
|
+
const item = await bdo.get(id);
|
|
40
|
+
<span>{item.product_name}</span>
|
|
284
41
|
|
|
285
|
-
|
|
286
|
-
|
|
42
|
+
// ✅ CORRECT — call .get()
|
|
43
|
+
<span>{item.product_name.get()}</span>
|
|
44
|
+
<span>{item.product_name.get() ?? "—"}</span>
|
|
287
45
|
```
|
|
288
46
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
### draftPatch
|
|
47
|
+
**Key distinction:** `useTable().rows` are plain objects (access `row.field` directly). `bdo.get()`/`bdo.list()` return ItemType proxies (must call `item.field.get()`).
|
|
292
48
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
async draftPatch(id: string, data: Partial<TEditable>): Promise<DraftResponseType>
|
|
297
|
-
```
|
|
49
|
+
### 3. Rendering Image/File/Reference values in detail pages
|
|
298
50
|
|
|
299
|
-
|
|
300
|
-
const draftResult = await product.draftPatch("prod_abc123", {
|
|
301
|
-
Price: 120,
|
|
302
|
-
DiscountPercent: 20,
|
|
303
|
-
});
|
|
51
|
+
These field types return complex objects from `.get()` — NOT renderable as React children.
|
|
304
52
|
|
|
305
|
-
|
|
306
|
-
|
|
53
|
+
```tsx
|
|
54
|
+
// ❌ WRONG — FileType/ImageFieldType are objects, not ReactNode
|
|
55
|
+
<span>{item.product_image.get()}</span> // ImageFieldType = FileType | null
|
|
56
|
+
<span>{item.attachments.get()}</span> // FileFieldType = FileType[]
|
|
57
|
+
<span>{item.category.get()}</span> // Reference = { _id, _name, ... }
|
|
307
58
|
|
|
308
|
-
|
|
59
|
+
// ✅ CORRECT — extract the renderable value
|
|
60
|
+
// Image: use ImageThumbnail component (NEVER use src="#")
|
|
61
|
+
import { ImageThumbnail } from "@/components/ui/image-thumbnail";
|
|
62
|
+
<ImageThumbnail boId={bdo.meta._id} instanceId={item._id} fieldId={bdo.product_image.id} value={item.product_image.get()} imgClassName="w-24 h-24 object-cover rounded" />
|
|
309
63
|
|
|
310
|
-
|
|
64
|
+
// Image: if you only need the filename as text
|
|
65
|
+
const img = item.product_image.get();
|
|
66
|
+
<span>{img ? (img as any).FileName : "No image"}</span>
|
|
311
67
|
|
|
312
|
-
|
|
68
|
+
// File array: use FilePreview component
|
|
69
|
+
import { FilePreview } from "@/components/ui/file-preview";
|
|
70
|
+
<FilePreview boId={bdo.meta._id} instanceId={item._id} fieldId={bdo.attachments.id} value={item.attachments.get()} />
|
|
313
71
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
72
|
+
// Reference: access _name
|
|
73
|
+
const ref = item.category.get();
|
|
74
|
+
<span>{ref ? (ref as any)._name : "—"}</span>
|
|
317
75
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
Price: 100,
|
|
321
|
-
DiscountPercent: 10,
|
|
322
|
-
});
|
|
76
|
+
// Number: format
|
|
77
|
+
<span>{(item.unit_price.get() ?? 0).toFixed(2)}</span>
|
|
323
78
|
|
|
324
|
-
|
|
325
|
-
|
|
79
|
+
// Boolean: display
|
|
80
|
+
<span>{item.is_active.get() ? "Yes" : "No"}</span>
|
|
326
81
|
```
|
|
327
82
|
|
|
328
|
-
|
|
83
|
+
### 4. Calling protected methods
|
|
329
84
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
Creates an `ItemType` wrapper synchronously (no API call). Useful for creating an empty item for form binding.
|
|
333
|
-
|
|
334
|
-
```typescript
|
|
335
|
-
createItem(data?: Partial<TEditable>): ItemType<TEditable, TReadonly>
|
|
336
|
-
```
|
|
85
|
+
Only methods the role has permission for are `public`. Check the BDO file.
|
|
337
86
|
|
|
338
87
|
```typescript
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
emptyItem.Price.set(0);
|
|
88
|
+
// ❌ WRONG — if delete is not in the role's BDO class
|
|
89
|
+
await bdo.delete(id); // TS2445: Property 'delete' is protected
|
|
342
90
|
|
|
343
|
-
|
|
344
|
-
|
|
91
|
+
// ✅ CORRECT — use api() for methods not on the BDO class
|
|
92
|
+
import { api } from "@ram_28/kf-ai-sdk/api";
|
|
93
|
+
await api(bdo.meta._id).delete(id);
|
|
345
94
|
```
|
|
346
95
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
### metric
|
|
350
|
-
|
|
351
|
-
Performs aggregation queries on records.
|
|
96
|
+
### 5. Not handling `.get()` return type (undefined)
|
|
352
97
|
|
|
353
98
|
```typescript
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
The `Type` field is added internally. Pass only `GroupBy`, `Metric`, and optional `Filter`.
|
|
99
|
+
// ❌ WRONG — might be undefined
|
|
100
|
+
const title: string = item.product_name.get();
|
|
358
101
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
GroupBy: [],
|
|
363
|
-
Metric: [{ Field: "_id", Type: "Count" }],
|
|
364
|
-
});
|
|
365
|
-
console.log("Total:", response.Data[0]["count__id"]);
|
|
366
|
-
|
|
367
|
-
// Group by category with multiple metrics
|
|
368
|
-
const byCategory = await product.metric({
|
|
369
|
-
GroupBy: ["Category"],
|
|
370
|
-
Metric: [
|
|
371
|
-
{ Field: "Stock", Type: "Sum" },
|
|
372
|
-
{ Field: "Price", Type: "Avg" },
|
|
373
|
-
],
|
|
374
|
-
});
|
|
375
|
-
byCategory.Data.forEach((row) => {
|
|
376
|
-
console.log(`${row.Category}: ${row["sum_Stock"]} stock, $${row["avg_Price"]} avg`);
|
|
377
|
-
});
|
|
102
|
+
// ✅ CORRECT — provide fallback
|
|
103
|
+
const title = item.product_name.get() ?? "";
|
|
104
|
+
const price = item.unit_price.get() ?? 0;
|
|
378
105
|
```
|
|
379
106
|
|
|
380
107
|
---
|
|
381
108
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
Creates pivot table aggregations with row and column dimensions.
|
|
109
|
+
## BDO Meta & Fields
|
|
385
110
|
|
|
386
111
|
```typescript
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
The `Type` field is added internally. Pass only `Row`, `Column`, `Metric`, and optional `Filter`.
|
|
112
|
+
const product = new AdminProduct();
|
|
113
|
+
product.meta._id; // "BDO_Product" — use as source in useTable, api()
|
|
114
|
+
product.meta.name; // "Product"
|
|
391
115
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const { RowHeader, ColumnHeader, Value } = response.Data;
|
|
400
|
-
|
|
401
|
-
RowHeader.forEach((row, ri) => {
|
|
402
|
-
ColumnHeader.forEach((col, ci) => {
|
|
403
|
-
console.log(`${row.Key} - ${col.Key}: ${Value[ri][ci]}`);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
116
|
+
// Field getters (all field types)
|
|
117
|
+
product.product_name.id // "product_name" — use in register(), watch(), setValue()
|
|
118
|
+
product.product_name.label // "product_name" — display label
|
|
119
|
+
product.product_name.required // boolean
|
|
120
|
+
product.product_name.readOnly // boolean
|
|
121
|
+
product.product_name.defaultValue // unknown
|
|
406
122
|
```
|
|
407
123
|
|
|
408
|
-
---
|
|
409
|
-
|
|
410
124
|
## Field Classes
|
|
411
125
|
|
|
412
|
-
|
|
126
|
+
| Class | Extra Getters | Notes |
|
|
127
|
+
|-------|--------------|-------|
|
|
128
|
+
| `StringField` | `length` | May have `Constraint.Enum` — see useForm Mistake #3 |
|
|
129
|
+
| `NumberField` | `integerPart`, `fractionPart` | |
|
|
130
|
+
| `BooleanField` | — | |
|
|
131
|
+
| `DateField` | — | Format: YYYY-MM-DD |
|
|
132
|
+
| `DateTimeField` | `precision` | Format: YYYY-MM-DDTHH:MM:SS |
|
|
133
|
+
| `SelectField` | **`options`**, `fetchOptions(instanceId)` | `.options` returns `{ value, label }[]` |
|
|
134
|
+
| `ReferenceField` | `fetchOptions(instanceId)`, `referenceBdo` | Value is object `{ _id, _name, ... }` |
|
|
135
|
+
| `TextField` | `format` | Long text |
|
|
136
|
+
| `UserField` | `fetchOptions(instanceId)` | Value: `{ _id, _name }` |
|
|
137
|
+
| `ImageField` | — | Value: `FileType \| null` |
|
|
138
|
+
| `FileField` | — | Value: `FileType[]` |
|
|
413
139
|
|
|
414
|
-
|
|
415
|
-
|-------|-------------|---------------|
|
|
416
|
-
| `StringField` | `"String"` | `length` |
|
|
417
|
-
| `NumberField` | `"Number"` | `integerPart`, `fractionPart` |
|
|
418
|
-
| `BooleanField` | `"Boolean"` | — |
|
|
419
|
-
| `DateField` | `"Date"` | — |
|
|
420
|
-
| `DateTimeField` | `"DateTime"` | `precision` |
|
|
421
|
-
| `SelectField<T>` | `"String"` | `options`, `fetchOptions()` |
|
|
422
|
-
| `ReferenceField<T>` | `"Reference"` | `referenceBdo`, `referenceFields`, `searchFields`, `fetchOptions()` |
|
|
423
|
-
| `TextField` | `"Text"` | `format` |
|
|
424
|
-
| `UserField` | `"User"` | `businessEntity` |
|
|
425
|
-
| `FileField` | `"File"` | — |
|
|
426
|
-
| `ArrayField<T>` | `"Array"` | `elementType` |
|
|
427
|
-
| `ObjectField<T>` | `"Object"` | `properties` |
|
|
428
|
-
|
|
429
|
-
### SelectField
|
|
430
|
-
|
|
431
|
-
Options from `Constraint.Enum` are available synchronously via the `options` getter:
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
product.Status.options;
|
|
435
|
-
// [{ value: "active", label: "active" }, { value: "inactive", label: "inactive" }]
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
Dynamic options can be fetched from the backend via `fetchOptions()`:
|
|
439
|
-
|
|
440
|
-
```typescript
|
|
441
|
-
async fetchOptions(instanceId?: string): Promise<SelectOptionType<T>[]>
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
const statusOptions = await product.Status.fetchOptions();
|
|
446
|
-
// [{ value: "active", label: "Active" }, { value: "inactive", label: "Inactive" }]
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
### ReferenceField
|
|
450
|
-
|
|
451
|
-
Reference configuration is derived from `View.DataObject` in the raw meta:
|
|
452
|
-
|
|
453
|
-
```typescript
|
|
454
|
-
product.SupplierInfo.referenceBdo; // "BDO_Supplier"
|
|
455
|
-
product.SupplierInfo.referenceFields; // ["_id", "SupplierName", "Email"]
|
|
456
|
-
product.SupplierInfo.searchFields; // ["SupplierName"]
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
Referenced records can be fetched via `fetchOptions()`:
|
|
460
|
-
|
|
461
|
-
```typescript
|
|
462
|
-
async fetchOptions(instanceId?: string): Promise<TRef[]>
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
const suppliers = await product.SupplierInfo.fetchOptions();
|
|
467
|
-
// [{ _id: "sup_1", SupplierName: "Acme Corp", Email: "contact@acme.com" }, ...]
|
|
468
|
-
```
|
|
140
|
+
**CRITICAL**: Only `SelectField` has `.options` getter. `StringField` with `Constraint.Enum` does NOT.
|
|
469
141
|
|
|
470
142
|
---
|
|
471
143
|
|
|
472
|
-
##
|
|
473
|
-
|
|
474
|
-
`ItemType<TEditable, TReadonly>` is what `get()`, `list()`, `create()`, and `createItem()` return. It wraps API response data with a Proxy, providing field accessors for every field.
|
|
475
|
-
|
|
476
|
-
### Field Accessor Access
|
|
477
|
-
|
|
478
|
-
```typescript
|
|
479
|
-
const item = await product.get("prod_abc123");
|
|
480
|
-
|
|
481
|
-
// Get value (NOT direct access — always use .get())
|
|
482
|
-
item.Title.get(); // "Wireless Headphones"
|
|
483
|
-
|
|
484
|
-
// Field metadata
|
|
485
|
-
item.Title.label; // "Product Title"
|
|
486
|
-
item.Title.required; // true
|
|
487
|
-
item.Title.readOnly; // false
|
|
488
|
-
item.Title.defaultValue; // undefined
|
|
489
|
-
item.Title.meta; // full raw backend meta (BaseFieldMetaType)
|
|
490
|
-
|
|
491
|
-
// Set value (only on editable fields)
|
|
492
|
-
item.Title.set("New Title");
|
|
493
|
-
|
|
494
|
-
// Validate (type + expression rules)
|
|
495
|
-
const result = item.Title.validate();
|
|
496
|
-
// { valid: true, errors: [] }
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### Direct `_id` Access
|
|
144
|
+
## Methods
|
|
500
145
|
|
|
501
|
-
`
|
|
146
|
+
All methods are `protected` on `BaseBdo`. Role BDOs selectively expose them as `public`.
|
|
502
147
|
|
|
503
148
|
```typescript
|
|
504
|
-
|
|
505
|
-
|
|
149
|
+
// Read
|
|
150
|
+
const item = await bdo.get(id); // ItemType — call .get() on fields
|
|
151
|
+
const items = await bdo.list(options?); // ItemType[] — call .get() on fields
|
|
152
|
+
const count = await bdo.count(options?); // number
|
|
506
153
|
|
|
507
|
-
|
|
154
|
+
// Write
|
|
155
|
+
const created = await bdo.create(data); // ItemType
|
|
156
|
+
const result = await bdo.update(id, data); // { _id: string }
|
|
157
|
+
const deleted = await bdo.delete(id); // { status: "success" }
|
|
508
158
|
|
|
509
|
-
|
|
159
|
+
// Draft
|
|
160
|
+
const draft = await bdo.draft(data); // { [field]: computed_value }
|
|
161
|
+
const draftPatch = await bdo.draftPatch(id, data);
|
|
162
|
+
const draftInt = await bdo.draftInteraction(data); // { _id, ...computed }
|
|
510
163
|
|
|
511
|
-
|
|
164
|
+
// Analytics
|
|
165
|
+
const metric = await bdo.metric({ GroupBy: [], Metric: [{ Field: "_id", Type: "Count" }] });
|
|
166
|
+
// Access: metric.Data[0]["count__id"] (key pattern: {type}_{Field})
|
|
512
167
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
label: string,
|
|
516
|
-
required: boolean,
|
|
517
|
-
readOnly: false, // always false for editable
|
|
518
|
-
defaultValue: unknown,
|
|
519
|
-
meta: BaseFieldMetaType,
|
|
520
|
-
get(): T | undefined,
|
|
521
|
-
set(value: T): void, // only on editable fields
|
|
522
|
-
validate(): ValidationResultType,
|
|
523
|
-
}
|
|
168
|
+
const pivot = await bdo.pivot({ Row: ["category"], Column: ["status"], Metric: [{ Field: "_id", Type: "Count" }] });
|
|
169
|
+
// Access: pivot.Data.RowHeader, pivot.Data.ColumnHeader, pivot.Data.Value[row][col]
|
|
524
170
|
```
|
|
525
171
|
|
|
526
|
-
|
|
172
|
+
### ListOptionsType
|
|
527
173
|
|
|
528
174
|
```typescript
|
|
529
|
-
{
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
get(): T | undefined,
|
|
536
|
-
validate(): ValidationResultType,
|
|
537
|
-
// no set()
|
|
175
|
+
interface ListOptionsType {
|
|
176
|
+
Field?: string[]; // Specific fields (omit for all)
|
|
177
|
+
Filter?: FilterType; // See useFilter docs
|
|
178
|
+
Sort?: Record<string, "ASC" | "DESC">[]; // [{ "field": "ASC" }]
|
|
179
|
+
Page?: number; // 1-indexed
|
|
180
|
+
PageSize?: number; // Default: 10
|
|
538
181
|
}
|
|
539
182
|
```
|
|
540
183
|
|
|
541
|
-
### Item Methods
|
|
542
|
-
|
|
543
|
-
```typescript
|
|
544
|
-
// Validate all non-readonly fields
|
|
545
|
-
item.validate();
|
|
546
|
-
// { valid: boolean, errors: string[] }
|
|
547
|
-
|
|
548
|
-
// Convert to plain object
|
|
549
|
-
item.toJSON();
|
|
550
|
-
// Partial<T> — raw data without accessors
|
|
551
|
-
```
|
|
552
|
-
|
|
553
184
|
---
|
|
554
185
|
|
|
555
|
-
##
|
|
186
|
+
## ItemType Accessor Pattern
|
|
556
187
|
|
|
557
|
-
|
|
188
|
+
`get()`, `list()`, `create()` return `ItemType` with Proxy-wrapped field accessors.
|
|
558
189
|
|
|
559
190
|
```typescript
|
|
560
|
-
|
|
561
|
-
readonly _id: string; // Business object ID (e.g., "BDO_Product")
|
|
562
|
-
readonly name: string; // Display name (e.g., "Product")
|
|
563
|
-
}
|
|
564
|
-
```
|
|
191
|
+
const item = await bdo.get("id123");
|
|
565
192
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
193
|
+
item._id; // "id123" — direct access (only _id)
|
|
194
|
+
item.product_name.get(); // string | undefined
|
|
195
|
+
item.product_name.set("New"); // editable fields only
|
|
196
|
+
item.product_name.label; // "product_name"
|
|
197
|
+
item.product_name.required; // boolean
|
|
198
|
+
item.toJSON(); // plain object
|
|
573
199
|
```
|
|
574
200
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
```typescript
|
|
578
|
-
interface SelectOptionType<T = string> {
|
|
579
|
-
value: T;
|
|
580
|
-
label: string;
|
|
581
|
-
disabled?: boolean;
|
|
582
|
-
}
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
### SystemFieldsType
|
|
201
|
+
## SystemFieldsType
|
|
586
202
|
|
|
587
203
|
```typescript
|
|
588
204
|
type SystemFieldsType = {
|
|
589
205
|
_id: string;
|
|
590
|
-
_created_at:
|
|
591
|
-
_modified_at:
|
|
592
|
-
_created_by:
|
|
593
|
-
_modified_by:
|
|
206
|
+
_created_at: string; // "YYYY-MM-DDTHH:MM:SS"
|
|
207
|
+
_modified_at: string;
|
|
208
|
+
_created_by: { _id: string; _name: string };
|
|
209
|
+
_modified_by: { _id: string; _name: string };
|
|
594
210
|
_version: string;
|
|
595
211
|
_m_version: string;
|
|
596
212
|
};
|
|
597
|
-
|
|
598
|
-
// Union of system field names (for Omit<> operations)
|
|
599
|
-
type SystemFields = keyof SystemFieldsType;
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
### BaseFieldMetaType
|
|
603
|
-
|
|
604
|
-
The raw backend meta shape stored by every field:
|
|
605
|
-
|
|
606
|
-
```typescript
|
|
607
|
-
interface BaseFieldMetaType {
|
|
608
|
-
_id: string; // Field identifier
|
|
609
|
-
Name: string; // Display name
|
|
610
|
-
Type: string; // Field type ("String", "Number", "Reference", etc.)
|
|
611
|
-
ReadOnly?: boolean;
|
|
612
|
-
Required?: boolean;
|
|
613
|
-
Constraint?: BaseConstraintType;
|
|
614
|
-
DefaultValue?: unknown;
|
|
615
|
-
}
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
### BaseConstraintType
|
|
619
|
-
|
|
620
|
-
```typescript
|
|
621
|
-
interface BaseConstraintType {
|
|
622
|
-
Required?: boolean;
|
|
623
|
-
PrimaryKey?: boolean;
|
|
624
|
-
DefaultValue?: unknown;
|
|
625
|
-
}
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
### EditableFieldAccessorType
|
|
629
|
-
|
|
630
|
-
```typescript
|
|
631
|
-
interface EditableFieldAccessorType<T> {
|
|
632
|
-
readonly label: string;
|
|
633
|
-
readonly required: boolean;
|
|
634
|
-
readonly readOnly: boolean;
|
|
635
|
-
readonly defaultValue: unknown;
|
|
636
|
-
readonly meta: BaseFieldMetaType;
|
|
637
|
-
get(): T | undefined;
|
|
638
|
-
set(value: T): void;
|
|
639
|
-
validate(): ValidationResultType;
|
|
640
|
-
}
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
### ReadonlyFieldAccessorType
|
|
644
|
-
|
|
645
|
-
```typescript
|
|
646
|
-
type ReadonlyFieldAccessorType<T> = {
|
|
647
|
-
readonly label: string;
|
|
648
|
-
readonly required: boolean;
|
|
649
|
-
readonly readOnly: boolean;
|
|
650
|
-
readonly defaultValue: unknown;
|
|
651
|
-
readonly meta: BaseFieldMetaType;
|
|
652
|
-
get(): T | undefined;
|
|
653
|
-
validate(): ValidationResultType;
|
|
654
|
-
};
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
---
|
|
658
|
-
|
|
659
|
-
## Complete Example
|
|
660
|
-
|
|
661
|
-
```typescript
|
|
662
|
-
import { AdminProduct } from "../bdo/admin/Product";
|
|
663
|
-
import type {
|
|
664
|
-
AdminProductEditableFieldType,
|
|
665
|
-
AdminProductFieldType,
|
|
666
|
-
} from "../bdo/admin/Product";
|
|
667
|
-
|
|
668
|
-
const product = new AdminProduct();
|
|
669
|
-
|
|
670
|
-
// ---- Field metadata ----
|
|
671
|
-
console.log(product.meta._id); // "BDO_Product"
|
|
672
|
-
console.log(product.Title.label); // "Product Title"
|
|
673
|
-
console.log(product.Title.required); // true
|
|
674
|
-
|
|
675
|
-
// ---- Get a single item ----
|
|
676
|
-
const item = await product.get("prod_abc123");
|
|
677
|
-
console.log(item._id); // "prod_abc123"
|
|
678
|
-
console.log(item.Title.get()); // "Wireless Headphones"
|
|
679
|
-
console.log(item.Price.get()); // 99.99
|
|
680
|
-
|
|
681
|
-
// ---- List items with filter ----
|
|
682
|
-
const items = await product.list({
|
|
683
|
-
Filter: {
|
|
684
|
-
Operator: "And",
|
|
685
|
-
Condition: [{
|
|
686
|
-
LHSField: product.Category.id,
|
|
687
|
-
Operator: "EQ",
|
|
688
|
-
RHSValue: "Electronics",
|
|
689
|
-
RHSType: "Constant",
|
|
690
|
-
}],
|
|
691
|
-
},
|
|
692
|
-
Sort: [{ [product.Price.id]: "ASC" }],
|
|
693
|
-
Page: 1,
|
|
694
|
-
PageSize: 20,
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
for (const item of items) {
|
|
698
|
-
console.log(item.Title.get(), item.Price.get());
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// ---- Count ----
|
|
702
|
-
const total = await product.count();
|
|
703
|
-
console.log(`Total products: ${total}`);
|
|
704
|
-
|
|
705
|
-
// ---- Create ----
|
|
706
|
-
const newItem = await product.create({
|
|
707
|
-
Title: "New Product",
|
|
708
|
-
Price: 49.99,
|
|
709
|
-
Category: "Books",
|
|
710
|
-
});
|
|
711
|
-
console.log(newItem._id); // New record ID
|
|
712
|
-
console.log(newItem.Title.get()); // "New Product"
|
|
713
|
-
|
|
714
|
-
// ---- Update ----
|
|
715
|
-
const updateResult = await product.update("prod_abc123", {
|
|
716
|
-
Price: 79.99,
|
|
717
|
-
});
|
|
718
|
-
console.log(updateResult._id); // "prod_abc123"
|
|
719
|
-
|
|
720
|
-
// ---- Delete ----
|
|
721
|
-
const deleteResult = await product.delete("prod_abc123");
|
|
722
|
-
console.log(deleteResult.status); // "success"
|
|
723
|
-
|
|
724
|
-
// ---- Validate ----
|
|
725
|
-
const emptyItem = product.createItem();
|
|
726
|
-
emptyItem.Title.set("");
|
|
727
|
-
const validation = emptyItem.validate();
|
|
728
|
-
console.log(validation.valid); // false
|
|
729
|
-
console.log(validation.errors); // ["Product Title is required"]
|
|
730
|
-
|
|
731
|
-
// ---- SelectField options ----
|
|
732
|
-
const statusOptions = await product.Status.fetchOptions();
|
|
733
|
-
statusOptions.forEach((opt) => {
|
|
734
|
-
console.log(`${opt.value}: ${opt.label}`);
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
// ---- ReferenceField options ----
|
|
738
|
-
const suppliers = await product.SupplierInfo.fetchOptions();
|
|
739
|
-
suppliers.forEach((sup) => {
|
|
740
|
-
console.log(`${sup._id}: ${sup.SupplierName}`);
|
|
741
|
-
});
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
---
|
|
745
|
-
|
|
746
|
-
## Common Mistakes
|
|
747
|
-
|
|
748
|
-
### 1. Guessing field names instead of reading BDO files
|
|
749
|
-
|
|
750
|
-
ALWAYS read the BDO `.ts` files (`src/bdo/{role}/*.ts`) BEFORE writing page code. Use the exact field names from the class — NEVER guess or invent field names.
|
|
751
|
-
|
|
752
|
-
```typescript
|
|
753
|
-
// ❌ WRONG — guessing field names that don't exist
|
|
754
|
-
bdo.used // TS2339: Property 'used' does not exist
|
|
755
|
-
bdo.remaining // TS2339: Property 'remaining' does not exist
|
|
756
|
-
bdo.meta_title // TS2339: actual field might be 'seo_title'
|
|
757
|
-
bdo.tags // TS2339: no such field exists
|
|
758
|
-
|
|
759
|
-
// ✅ CORRECT — read the BDO file first, use exact names
|
|
760
|
-
// Check src/bdo/employee/LeaveBalance.ts for actual fields
|
|
761
|
-
bdo.UsedLeaves // matches the actual field in the BDO class
|
|
762
|
-
bdo.RemainingDays // matches the actual field in the BDO class
|
|
763
213
|
```
|
|
764
214
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
Check `src/bdo/{role}/index.ts` to see which BDO classes actually exist before importing.
|
|
215
|
+
## API Types
|
|
768
216
|
|
|
769
217
|
```typescript
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
### 3. Calling protected methods
|
|
778
|
-
|
|
779
|
-
All `BaseBdo` methods are `protected` by default. Only the methods the role has permission for are re-exposed as `public` in the generated class. Read the BDO file to see which methods are available.
|
|
780
|
-
|
|
781
|
-
```typescript
|
|
782
|
-
// ❌ WRONG — if delete is not in the role's BDO class
|
|
783
|
-
await bdo.delete(id); // TS2445: Property 'delete' is protected
|
|
784
|
-
|
|
785
|
-
// ✅ CORRECT — use api() for methods not exposed on the BDO class
|
|
786
|
-
import { api } from "@ram_28/kf-ai-sdk/api";
|
|
787
|
-
await api(bdo.meta._id).delete(id);
|
|
788
|
-
|
|
789
|
-
// ✅ CORRECT — for create/update, prefer useForm
|
|
790
|
-
const { handleSubmit } = useForm({ bdo });
|
|
791
|
-
```
|
|
792
|
-
|
|
793
|
-
### 4. Not handling `.get()` return type
|
|
794
|
-
|
|
795
|
-
`ItemType` field `.get()` returns `T | undefined`. Always handle the undefined case.
|
|
796
|
-
|
|
797
|
-
```typescript
|
|
798
|
-
// ❌ WRONG — might be undefined
|
|
799
|
-
const title: string = item.Title.get();
|
|
800
|
-
|
|
801
|
-
// ✅ CORRECT — provide fallback
|
|
802
|
-
const title = item.Title.get() ?? "";
|
|
803
|
-
const price = item.Price.get() ?? 0;
|
|
804
|
-
const active = item.IsActive.get() ?? false;
|
|
218
|
+
interface CreateUpdateResponseType { _id: string; }
|
|
219
|
+
interface DeleteResponseType { status: "success"; }
|
|
220
|
+
interface FileType { _id: string; _name: string; FileName: string; FileExtension: string; Size: number; ContentType: string; }
|
|
221
|
+
type ImageFieldType = FileType | null;
|
|
222
|
+
type FileFieldType = FileType[];
|
|
223
|
+
type AggregationType = "Sum" | "Avg" | "Count" | "Max" | "Min" | "DistinctCount" | "BlankCount" | "NotBlankCount" | "Concat" | "DistinctConcat";
|
|
805
224
|
```
|