@ram_28/kf-ai-sdk 2.0.1 → 2.0.2

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 (134) hide show
  1. package/README.md +36 -39
  2. package/dist/api/client.d.ts.map +1 -1
  3. package/dist/api/metadata.d.ts +26 -22
  4. package/dist/api/metadata.d.ts.map +1 -1
  5. package/dist/api.cjs +1 -1
  6. package/dist/api.mjs +3 -3
  7. package/dist/auth.cjs +1 -1
  8. package/dist/auth.mjs +2 -2
  9. package/dist/base-types.d.ts +1 -1
  10. package/dist/base-types.d.ts.map +1 -1
  11. package/dist/bdo/core/BaseBdo.d.ts +8 -20
  12. package/dist/bdo/core/BaseBdo.d.ts.map +1 -1
  13. package/dist/bdo/core/Item.d.ts +8 -44
  14. package/dist/bdo/core/Item.d.ts.map +1 -1
  15. package/dist/bdo/core/types.d.ts +124 -41
  16. package/dist/bdo/core/types.d.ts.map +1 -1
  17. package/dist/bdo/fields/ArrayField.d.ts +5 -4
  18. package/dist/bdo/fields/ArrayField.d.ts.map +1 -1
  19. package/dist/bdo/fields/BaseField.d.ts +14 -19
  20. package/dist/bdo/fields/BaseField.d.ts.map +1 -1
  21. package/dist/bdo/fields/BooleanField.d.ts +3 -4
  22. package/dist/bdo/fields/BooleanField.d.ts.map +1 -1
  23. package/dist/bdo/fields/DateField.d.ts +3 -4
  24. package/dist/bdo/fields/DateField.d.ts.map +1 -1
  25. package/dist/bdo/fields/DateTimeField.d.ts +5 -4
  26. package/dist/bdo/fields/DateTimeField.d.ts.map +1 -1
  27. package/dist/bdo/fields/FileField.d.ts +18 -0
  28. package/dist/bdo/fields/FileField.d.ts.map +1 -0
  29. package/dist/bdo/fields/NumberField.d.ts +6 -4
  30. package/dist/bdo/fields/NumberField.d.ts.map +1 -1
  31. package/dist/bdo/fields/ObjectField.d.ts +5 -4
  32. package/dist/bdo/fields/ObjectField.d.ts.map +1 -1
  33. package/dist/bdo/fields/ReferenceField.d.ts +13 -14
  34. package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
  35. package/dist/bdo/fields/SelectField.d.ts +9 -16
  36. package/dist/bdo/fields/SelectField.d.ts.map +1 -1
  37. package/dist/bdo/fields/StringField.d.ts +6 -4
  38. package/dist/bdo/fields/StringField.d.ts.map +1 -1
  39. package/dist/bdo/fields/TextAreaField.d.ts +1 -18
  40. package/dist/bdo/fields/TextAreaField.d.ts.map +1 -1
  41. package/dist/bdo/fields/TextField.d.ts +22 -0
  42. package/dist/bdo/fields/TextField.d.ts.map +1 -0
  43. package/dist/bdo/fields/UserField.d.ts +20 -0
  44. package/dist/bdo/fields/UserField.d.ts.map +1 -0
  45. package/dist/bdo/fields/index.d.ts +5 -2
  46. package/dist/bdo/fields/index.d.ts.map +1 -1
  47. package/dist/bdo/index.d.ts +2 -2
  48. package/dist/bdo/index.d.ts.map +1 -1
  49. package/dist/bdo.cjs +1 -1
  50. package/dist/bdo.d.ts +1 -5
  51. package/dist/bdo.d.ts.map +1 -1
  52. package/dist/bdo.mjs +312 -242
  53. package/dist/bdo.types.d.ts +2 -3
  54. package/dist/bdo.types.d.ts.map +1 -1
  55. package/dist/client-Bo-RLKJi.cjs +1 -0
  56. package/dist/client-eA4VvNTo.js +178 -0
  57. package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
  58. package/dist/components/hooks/useForm/createItemProxy.d.ts.map +1 -1
  59. package/dist/components/hooks/useForm/createResolver.d.ts +12 -2
  60. package/dist/components/hooks/useForm/createResolver.d.ts.map +1 -1
  61. package/dist/components/hooks/useForm/index.d.ts +1 -1
  62. package/dist/components/hooks/useForm/index.d.ts.map +1 -1
  63. package/dist/components/hooks/useForm/types.d.ts +62 -6
  64. package/dist/components/hooks/useForm/types.d.ts.map +1 -1
  65. package/dist/components/hooks/useForm/useForm.d.ts +1 -27
  66. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
  67. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
  68. package/dist/{constants-DaX7GLgl.js → constants-CM9xOACN.js} +37 -13
  69. package/dist/constants-D0J69if5.cjs +1 -0
  70. package/dist/filter.cjs +1 -1
  71. package/dist/filter.mjs +2 -2
  72. package/dist/form.cjs +1 -1
  73. package/dist/form.d.ts +1 -1
  74. package/dist/form.d.ts.map +1 -1
  75. package/dist/form.mjs +879 -809
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/{metadata-Bz8zJqC1.cjs → metadata-BN57S6W9.cjs} +1 -1
  79. package/dist/{metadata-VbQzyD2C.js → metadata-P7DGCgIG.js} +1 -1
  80. package/dist/table.cjs +1 -1
  81. package/dist/table.mjs +74 -70
  82. package/dist/types/base-fields.d.ts +24 -59
  83. package/dist/types/base-fields.d.ts.map +1 -1
  84. package/dist/useFilter-Bg0jvIL6.cjs +1 -0
  85. package/dist/useFilter-DPNLKY3H.js +118 -0
  86. package/dist/utils/formatting.d.ts +10 -1
  87. package/dist/utils/formatting.d.ts.map +1 -1
  88. package/docs/api.md +140 -572
  89. package/docs/bdo.md +742 -0
  90. package/docs/useAuth.md +10 -4
  91. package/docs/useFilter.md +40 -40
  92. package/docs/useForm.md +91 -62
  93. package/docs/useTable.md +38 -38
  94. package/package.json +1 -1
  95. package/sdk/api/client.ts +52 -65
  96. package/sdk/api/metadata.ts +22 -20
  97. package/sdk/base-types.ts +5 -9
  98. package/sdk/bdo/core/BaseBdo.ts +13 -38
  99. package/sdk/bdo/core/Item.ts +34 -56
  100. package/sdk/bdo/core/types.ts +147 -45
  101. package/sdk/bdo/fields/ArrayField.ts +9 -5
  102. package/sdk/bdo/fields/BaseField.ts +19 -29
  103. package/sdk/bdo/fields/BooleanField.ts +4 -5
  104. package/sdk/bdo/fields/DateField.ts +4 -5
  105. package/sdk/bdo/fields/DateTimeField.ts +9 -5
  106. package/sdk/bdo/fields/FileField.ts +39 -0
  107. package/sdk/bdo/fields/NumberField.ts +8 -5
  108. package/sdk/bdo/fields/ObjectField.ts +9 -5
  109. package/sdk/bdo/fields/ReferenceField.ts +22 -28
  110. package/sdk/bdo/fields/SelectField.ts +13 -26
  111. package/sdk/bdo/fields/StringField.ts +10 -5
  112. package/sdk/bdo/fields/TextAreaField.ts +3 -32
  113. package/sdk/bdo/fields/TextField.ts +42 -0
  114. package/sdk/bdo/fields/UserField.ts +52 -0
  115. package/sdk/bdo/fields/index.ts +5 -2
  116. package/sdk/bdo/index.ts +19 -4
  117. package/sdk/bdo.ts +4 -31
  118. package/sdk/bdo.types.ts +26 -21
  119. package/sdk/components/hooks/useFilter/useFilter.ts +13 -30
  120. package/sdk/components/hooks/useForm/createItemProxy.ts +47 -11
  121. package/sdk/components/hooks/useForm/createResolver.ts +65 -6
  122. package/sdk/components/hooks/useForm/index.ts +3 -0
  123. package/sdk/components/hooks/useForm/types.ts +75 -6
  124. package/sdk/components/hooks/useForm/useForm.ts +35 -50
  125. package/sdk/components/hooks/useTable/useTable.ts +10 -2
  126. package/sdk/form.ts +1 -12
  127. package/sdk/index.ts +3 -3
  128. package/sdk/types/base-fields.ts +28 -72
  129. package/sdk/utils/formatting.ts +13 -1
  130. package/dist/client-BIkaIr2y.js +0 -217
  131. package/dist/client-DxjRcEtN.cjs +0 -1
  132. package/dist/constants-C49b9Exc.cjs +0 -1
  133. package/dist/useFilter-DzpP_ag0.cjs +0 -1
  134. package/dist/useFilter-H5bgAZQF.js +0 -120
package/sdk/api/client.ts CHANGED
@@ -161,24 +161,31 @@ export function getApiBaseUrl(): string {
161
161
  return apiConfig.baseUrl || "";
162
162
  }
163
163
 
164
+ /**
165
+ * Parse error response body and throw an Error with the server's message.
166
+ */
167
+ async function throwApiError(response: Response, context: string): Promise<never> {
168
+ let errorBody: any;
169
+ try { errorBody = await response.json(); } catch {}
170
+ const message = errorBody?.error || errorBody?.message || response.statusText;
171
+ throw new Error(`${context}: ${message}`);
172
+ }
173
+
164
174
  /**
165
175
  * Create a resource client for the specified Business Object
166
176
  * @param bo_id - Business Object identifier (e.g., "user", "leave", "vendor")
167
177
  * @returns Resource client with CRUD operations matching API spec
168
178
  */
169
179
  export function api<T = any>(bo_id: string): ResourceClientType<T> {
170
- const baseUrl = apiConfig.baseUrl;
171
- const defaultHeaders = apiConfig.headers;
172
-
173
180
  return {
174
181
  async get(id: string): Promise<T> {
175
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/${id}/read`, {
182
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/${id}/read`, {
176
183
  method: "GET",
177
- headers: defaultHeaders,
184
+ headers: getDefaultHeaders(),
178
185
  });
179
186
 
180
187
  if (!response.ok) {
181
- throw new Error(`Failed to get ${bo_id} ${id}: ${response.statusText}`);
188
+ await throwApiError(response, `Failed to get ${bo_id} ${id}`);
182
189
  }
183
190
 
184
191
  const responseData: ReadResponseType<T> = await response.json();
@@ -188,45 +195,41 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
188
195
  async create(
189
196
  data: Partial<T> & { _id?: string }
190
197
  ): Promise<CreateUpdateResponseType> {
191
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/create`, {
198
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/create`, {
192
199
  method: "POST",
193
- headers: defaultHeaders,
200
+ headers: getDefaultHeaders(),
194
201
  body: JSON.stringify(data),
195
202
  });
196
203
 
197
204
  if (!response.ok) {
198
- throw new Error(`Failed to create ${bo_id}: ${response.statusText}`);
205
+ await throwApiError(response, `Failed to create ${bo_id}`);
199
206
  }
200
207
 
201
208
  return response.json();
202
209
  },
203
210
 
204
211
  async update(id: string, data: Partial<T>): Promise<CreateUpdateResponseType> {
205
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/${id}/update`, {
212
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/${id}/update`, {
206
213
  method: "POST",
207
- headers: defaultHeaders,
214
+ headers: getDefaultHeaders(),
208
215
  body: JSON.stringify(data),
209
216
  });
210
217
 
211
218
  if (!response.ok) {
212
- throw new Error(
213
- `Failed to update ${bo_id} ${id}: ${response.statusText}`
214
- );
219
+ await throwApiError(response, `Failed to update ${bo_id} ${id}`);
215
220
  }
216
221
 
217
222
  return response.json();
218
223
  },
219
224
 
220
225
  async delete(id: string): Promise<DeleteResponseType> {
221
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/${id}/delete`, {
226
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/${id}/delete`, {
222
227
  method: "DELETE",
223
- headers: defaultHeaders,
228
+ headers: getDefaultHeaders(),
224
229
  });
225
230
 
226
231
  if (!response.ok) {
227
- throw new Error(
228
- `Failed to delete ${bo_id} ${id}: ${response.statusText}`
229
- );
232
+ await throwApiError(response, `Failed to delete ${bo_id} ${id}`);
230
233
  }
231
234
 
232
235
  return response.json();
@@ -238,14 +241,14 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
238
241
  ...options,
239
242
  };
240
243
 
241
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/list`, {
244
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/list`, {
242
245
  method: "POST",
243
- headers: defaultHeaders,
246
+ headers: getDefaultHeaders(),
244
247
  body: JSON.stringify(requestBody),
245
248
  });
246
249
 
247
250
  if (!response.ok) {
248
- throw new Error(`Failed to list ${bo_id}: ${response.statusText}`);
251
+ await throwApiError(response, `Failed to list ${bo_id}`);
249
252
  }
250
253
 
251
254
  const responseData: ListResponseType<T> = await response.json();
@@ -261,14 +264,14 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
261
264
  ...(options?.Filter && { Filter: options.Filter }),
262
265
  };
263
266
 
264
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/metric`, {
267
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/metric`, {
265
268
  method: "POST",
266
- headers: defaultHeaders,
269
+ headers: getDefaultHeaders(),
267
270
  body: JSON.stringify(requestBody),
268
271
  });
269
272
 
270
273
  if (!response.ok) {
271
- throw new Error(`Failed to count ${bo_id}: ${response.statusText}`);
274
+ await throwApiError(response, `Failed to count ${bo_id}`);
272
275
  }
273
276
 
274
277
  const result = await response.json();
@@ -282,16 +285,14 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
282
285
  // ============================================================
283
286
 
284
287
  async draft(data: Partial<T>): Promise<DraftResponseType> {
285
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/draft`, {
288
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/draft`, {
286
289
  method: "POST",
287
- headers: defaultHeaders,
290
+ headers: getDefaultHeaders(),
288
291
  body: JSON.stringify(data),
289
292
  });
290
293
 
291
294
  if (!response.ok) {
292
- throw new Error(
293
- `Failed to create draft for ${bo_id}: ${response.statusText}`
294
- );
295
+ await throwApiError(response, `Failed to create draft for ${bo_id}`);
295
296
  }
296
297
 
297
298
  return response.json();
@@ -301,32 +302,28 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
301
302
  id: string,
302
303
  data: Partial<T>
303
304
  ): Promise<CreateUpdateResponseType> {
304
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/${id}/draft`, {
305
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/${id}/draft`, {
305
306
  method: "POST",
306
- headers: defaultHeaders,
307
+ headers: getDefaultHeaders(),
307
308
  body: JSON.stringify(data),
308
309
  });
309
310
 
310
311
  if (!response.ok) {
311
- throw new Error(
312
- `Failed to update draft for ${bo_id} ${id}: ${response.statusText}`
313
- );
312
+ await throwApiError(response, `Failed to update draft for ${bo_id} ${id}`);
314
313
  }
315
314
 
316
315
  return response.json();
317
316
  },
318
317
 
319
318
  async draftPatch(id: string, data: Partial<T>): Promise<DraftResponseType> {
320
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/${id}/draft`, {
319
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/${id}/draft`, {
321
320
  method: "PATCH",
322
- headers: defaultHeaders,
321
+ headers: getDefaultHeaders(),
323
322
  body: JSON.stringify(data),
324
323
  });
325
324
 
326
325
  if (!response.ok) {
327
- throw new Error(
328
- `Failed to patch draft for ${bo_id} ${id}: ${response.statusText}`
329
- );
326
+ await throwApiError(response, `Failed to patch draft for ${bo_id} ${id}`);
330
327
  }
331
328
 
332
329
  return response.json();
@@ -335,16 +332,14 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
335
332
  async draftInteraction(
336
333
  data: Partial<T> & { _id?: string }
337
334
  ): Promise<DraftResponseType & { _id: string }> {
338
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/draft`, {
335
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/draft`, {
339
336
  method: "PATCH",
340
- headers: defaultHeaders,
337
+ headers: getDefaultHeaders(),
341
338
  body: JSON.stringify(data),
342
339
  });
343
340
 
344
341
  if (!response.ok) {
345
- throw new Error(
346
- `Failed to create interactive draft for ${bo_id}: ${response.statusText}`
347
- );
342
+ await throwApiError(response, `Failed to create interactive draft for ${bo_id}`);
348
343
  }
349
344
 
350
345
  const json = await response.json();
@@ -368,16 +363,14 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
368
363
  ...options,
369
364
  };
370
365
 
371
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/metric`, {
366
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/metric`, {
372
367
  method: "POST",
373
- headers: defaultHeaders,
368
+ headers: getDefaultHeaders(),
374
369
  body: JSON.stringify(requestBody),
375
370
  });
376
371
 
377
372
  if (!response.ok) {
378
- throw new Error(
379
- `Failed to get metrics for ${bo_id}: ${response.statusText}`
380
- );
373
+ await throwApiError(response, `Failed to get metrics for ${bo_id}`);
381
374
  }
382
375
 
383
376
  return response.json();
@@ -389,16 +382,14 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
389
382
  ...options,
390
383
  };
391
384
 
392
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/pivot`, {
385
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/pivot`, {
393
386
  method: "POST",
394
- headers: defaultHeaders,
387
+ headers: getDefaultHeaders(),
395
388
  body: JSON.stringify(requestBody),
396
389
  });
397
390
 
398
391
  if (!response.ok) {
399
- throw new Error(
400
- `Failed to get pivot data for ${bo_id}: ${response.statusText}`
401
- );
392
+ await throwApiError(response, `Failed to get pivot data for ${bo_id}`);
402
393
  }
403
394
 
404
395
  return response.json();
@@ -409,15 +400,13 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
409
400
  // ============================================================
410
401
 
411
402
  async fields(): Promise<FieldsResponseType> {
412
- const response = await fetch(`${baseUrl}/api/app/${bo_id}/fields`, {
403
+ const response = await fetch(`${getApiBaseUrl()}/api/app/${bo_id}/fields`, {
413
404
  method: "GET",
414
- headers: defaultHeaders,
405
+ headers: getDefaultHeaders(),
415
406
  });
416
407
 
417
408
  if (!response.ok) {
418
- throw new Error(
419
- `Failed to get fields for ${bo_id}: ${response.statusText}`
420
- );
409
+ await throwApiError(response, `Failed to get fields for ${bo_id}`);
421
410
  }
422
411
 
423
412
  return response.json();
@@ -428,17 +417,15 @@ export function api<T = any>(bo_id: string): ResourceClientType<T> {
428
417
  fieldId: string
429
418
  ): Promise<TResult[]> {
430
419
  const response = await fetch(
431
- `${baseUrl}/api/app/${bo_id}/${instanceId}/field/${fieldId}/fetch`,
420
+ `${getApiBaseUrl()}/api/app/${bo_id}/${instanceId}/field/${fieldId}/fetch`,
432
421
  {
433
422
  method: "GET",
434
- headers: defaultHeaders,
423
+ headers: getDefaultHeaders(),
435
424
  }
436
425
  );
437
426
 
438
427
  if (!response.ok) {
439
- throw new Error(
440
- `Failed to fetch field ${fieldId} for ${bo_id}: ${response.statusText}`
441
- );
428
+ await throwApiError(response, `Failed to fetch field ${fieldId} for ${bo_id}`);
442
429
  }
443
430
 
444
431
  const responseData: { Data: TResult[] } = await response.json();
@@ -81,20 +81,6 @@ export interface MetadataItemType {
81
81
  *
82
82
  * @param options - Optional list options (filters, sorting, pagination)
83
83
  * @returns Promise resolving to list of metadata items
84
- *
85
- * @example
86
- * ```typescript
87
- * // List all metadata
88
- * const all = await listMetadata();
89
- *
90
- * // List with filters
91
- * const businessObjects = await listMetadata({
92
- * Filter: {
93
- * Operator: "AND",
94
- * Condition: [{ LhsField: "Kind", Operator: "eq", RhsValue: "BusinessObject" }]
95
- * }
96
- * });
97
- * ```
98
84
  */
99
85
  export async function listMetadata(
100
86
  options?: ListOptionsType
@@ -131,18 +117,34 @@ export async function listMetadata(
131
117
  }
132
118
 
133
119
  /**
134
- * Field metadata structure
120
+ * Field metadata structure — matches new Constraint/View/Property backend shape
135
121
  */
136
122
  export interface FieldMetadataType {
137
123
  Id: string;
138
124
  Name: string;
139
125
  Type: string;
126
+ ReadOnly?: boolean;
140
127
  Required?: boolean;
141
- Unique?: boolean;
142
- Computed?: boolean;
143
- Values?: {
144
- Mode: "Static" | "Dynamic";
145
- Items?: Array<{ Value: string; Label: string }>;
128
+ Constraint?: {
129
+ Required?: boolean;
130
+ Enum?: string[];
131
+ Length?: number;
132
+ IntegerPart?: number;
133
+ FractionPart?: number;
134
+ PrimaryKey?: boolean;
135
+ DefaultValue?: unknown;
136
+ Precision?: "Second" | "Millisecond";
137
+ Format?: "Plain" | "Markdown";
138
+ };
139
+ View?: {
140
+ DataObject?: { Type: string; Id: string };
141
+ Fields?: Record<string, { Type: string }>;
142
+ Search?: string[];
143
+ Filter?: Record<string, unknown>;
144
+ Sort?: unknown[];
145
+ BusinessEntity?: string;
146
146
  };
147
+ Property?: FieldMetadataType | Record<string, FieldMetadataType>;
148
+ DefaultValue?: unknown;
147
149
  [key: string]: any;
148
150
  }
package/sdk/base-types.ts CHANGED
@@ -6,11 +6,11 @@
6
6
  export type {
7
7
  // String fields
8
8
  StringFieldType,
9
+ TextFieldType,
9
10
  TextAreaFieldType,
10
11
 
11
12
  // Numeric fields
12
13
  NumberFieldType,
13
- LongFieldType,
14
14
 
15
15
  // Boolean field
16
16
  BooleanFieldType,
@@ -20,21 +20,17 @@ export type {
20
20
  DateTimeFieldType,
21
21
 
22
22
  // Complex fields
23
- CurrencyFieldType,
24
- CurrencyValueType,
25
- JSONFieldType,
26
23
  ReferenceFieldType,
27
24
  ExtractReferenceType,
28
25
  ExtractFetchFieldType,
29
26
 
30
- // JSON types
31
- JSONValueType,
32
- JSONObjectType,
33
- JSONArrayType,
34
-
35
27
  // Select fields
36
28
  SelectFieldType,
37
29
 
30
+ // New field types
31
+ UserFieldType,
32
+ FileFieldType,
33
+
38
34
  // Container types
39
35
  ArrayFieldType,
40
36
  ObjectFieldType,
@@ -13,22 +13,19 @@ import type {
13
13
  PivotResponseType,
14
14
  DraftResponseType,
15
15
  } from "../../types/common";
16
- import type { StringFieldType, SystemFields, UserRefType } from "../../types/base-fields";
16
+ import type { SystemFields } from "../../types/base-fields";
17
17
  import { api } from "../../api/client";
18
18
  import { Item, type ItemType } from "./Item";
19
19
  import { BaseField } from "../fields/BaseField";
20
20
  import { StringField } from "../fields/StringField";
21
21
  import { DateTimeField } from "../fields/DateTimeField";
22
- import { ReferenceField } from "../fields/ReferenceField";
22
+ import { UserField } from "../fields/UserField";
23
23
  import type { ValidationResultType, BdoMetaType } from "./types";
24
24
  import { ExpressionEngine, type BDOMetadata } from "../expressions";
25
25
 
26
26
  // Re-export SystemFields type for consumers
27
27
  export type { SystemFields } from "../../types/base-fields";
28
28
 
29
- // System BDO ID for user references
30
- const USER_BO_ID = "SYS_User";
31
-
32
29
  /**
33
30
  * Abstract base class for Business Data Objects
34
31
  *
@@ -38,18 +35,6 @@ const USER_BO_ID = "SYS_User";
38
35
  * @template TEntity - The full entity type with all fields
39
36
  * @template TEditable - Fields that this role can create/update (defaults to TEntity without system fields)
40
37
  * @template TReadonly - Fields that this role can only read (defaults to empty)
41
- *
42
- * @example
43
- * ```typescript
44
- * class AdminProduct extends BaseBdo<ProductType> {
45
- * readonly meta = { _id: "BDO_Product", name: "Product" };
46
- *
47
- * // Re-expose only methods this role can use
48
- * public async get(id: StringFieldType) { return super.get(id); }
49
- * public async list(options?: ListOptionsType) { return super.list(options); }
50
- * // create, update, delete NOT exposed - TypeScript error if called
51
- * }
52
- * ```
53
38
  */
54
39
  export abstract class BaseBdo<
55
40
  TEntity extends Record<string, unknown>,
@@ -65,23 +50,13 @@ export abstract class BaseBdo<
65
50
  // SYSTEM FIELDS (inherited by all BDOs)
66
51
  // ============================================================
67
52
 
68
- readonly _id = new StringField({ id: "_id", label: "ID" });
69
- readonly _created_at = new DateTimeField({ id: "_created_at", label: "Created At" });
70
- readonly _modified_at = new DateTimeField({ id: "_modified_at", label: "Modified At" });
71
- readonly _created_by = new ReferenceField<UserRefType>({
72
- id: "_created_by",
73
- label: "Created By",
74
- referenceBdo: USER_BO_ID,
75
- referenceFields: ["_id", "username"],
76
- });
77
- readonly _modified_by = new ReferenceField<UserRefType>({
78
- id: "_modified_by",
79
- label: "Modified By",
80
- referenceBdo: USER_BO_ID,
81
- referenceFields: ["_id", "username"],
82
- });
83
- readonly _version = new StringField({ id: "_version", label: "Version" });
84
- readonly _m_version = new StringField({ id: "_m_version", label: "Metadata Version" });
53
+ readonly _id = new StringField({ _id: "_id", Name: "ID", Type: "String", ReadOnly: true });
54
+ readonly _created_at = new DateTimeField({ _id: "_created_at", Name: "Created At", Type: "DateTime", ReadOnly: true });
55
+ readonly _modified_at = new DateTimeField({ _id: "_modified_at", Name: "Modified At", Type: "DateTime", ReadOnly: true });
56
+ readonly _created_by = new UserField({ _id: "_created_by", Name: "Created By", Type: "User", ReadOnly: true });
57
+ readonly _modified_by = new UserField({ _id: "_modified_by", Name: "Modified By", Type: "User", ReadOnly: true });
58
+ readonly _version = new StringField({ _id: "_version", Name: "Version", Type: "String", ReadOnly: true });
59
+ readonly _m_version = new StringField({ _id: "_m_version", Name: "Metadata Version", Type: "String", ReadOnly: true });
85
60
 
86
61
  // ============================================================
87
62
  // FIELD DEFINITIONS (auto-discovered)
@@ -190,7 +165,7 @@ export abstract class BaseBdo<
190
165
  /**
191
166
  * Get a single record by ID
192
167
  */
193
- protected async get(id: StringFieldType): Promise<ItemType<TEditable, TReadonly>> {
168
+ protected async get(id: string): Promise<ItemType<TEditable, TReadonly>> {
194
169
  const data = await api<TEditable & TReadonly>(this.meta._id).get(id);
195
170
  return new Item<TEditable & TReadonly>(this, data as Partial<TEditable & TReadonly>) as ItemType<TEditable, TReadonly>;
196
171
  }
@@ -237,7 +212,7 @@ export abstract class BaseBdo<
237
212
  * Update an existing record
238
213
  */
239
214
  protected async update(
240
- id: StringFieldType,
215
+ id: string,
241
216
  data: Partial<TEditable>
242
217
  ): Promise<CreateUpdateResponseType> {
243
218
  return api<TEntity>(this.meta._id).update(id, data as Partial<TEntity>);
@@ -250,7 +225,7 @@ export abstract class BaseBdo<
250
225
  /**
251
226
  * Delete a record by ID
252
227
  */
253
- protected async delete(id: StringFieldType): Promise<DeleteResponseType> {
228
+ protected async delete(id: string): Promise<DeleteResponseType> {
254
229
  return api<TEntity>(this.meta._id).delete(id);
255
230
  }
256
231
 
@@ -287,7 +262,7 @@ export abstract class BaseBdo<
287
262
  * Patch an existing draft - compute fields during editing
288
263
  */
289
264
  protected async draftPatch(
290
- id: StringFieldType,
265
+ id: string,
291
266
  data: Partial<TEditable>
292
267
  ): Promise<DraftResponseType> {
293
268
  return api<TEntity>(this.meta._id).draftPatch(id, data as Partial<TEntity>);
@@ -3,7 +3,13 @@
3
3
  // Wraps a record with field accessors for type-safe manipulation
4
4
  // ============================================================
5
5
 
6
- import type { ValidationResultType, FieldMetaType } from "./types";
6
+ import type {
7
+ ValidationResultType,
8
+ BaseFieldMetaType,
9
+ EditableFieldAccessorType,
10
+ ReadonlyFieldAccessorType,
11
+ FieldAccessorType,
12
+ } from "./types";
7
13
  import type { BaseField } from "../fields/BaseField";
8
14
 
9
15
  /**
@@ -19,32 +25,8 @@ interface BdoLike {
19
25
  ): ValidationResultType;
20
26
  }
21
27
 
22
- /**
23
- * Editable field accessor — has get(), set(), validate()
24
- */
25
- export interface EditableFieldAccessorType<T> {
26
- readonly meta: FieldMetaType;
27
- get(): T | undefined;
28
- set(value: T): void;
29
- validate(): ValidationResultType;
30
- }
31
-
32
- /**
33
- * Readonly field accessor — has get(), validate(), NO set()
34
- */
35
- export interface ReadonlyFieldAccessorType<T> {
36
- readonly meta: FieldMetaType;
37
- get(): T | undefined;
38
- validate(): ValidationResultType;
39
- }
40
-
41
- /**
42
- * Field accessor with get/set/validate methods + meta object (union type)
43
- */
44
- export type FieldAccessorType<T> = EditableFieldAccessorType<T> | ReadonlyFieldAccessorType<T>;
45
-
46
- // Re-export FieldMetaType for convenience
47
- export type { FieldMetaType };
28
+ // Re-export accessor types for convenience
29
+ export type { EditableFieldAccessorType, ReadonlyFieldAccessorType, FieldAccessorType, BaseFieldMetaType };
48
30
 
49
31
  /**
50
32
  * Create editable accessor type for each field in TEditable
@@ -78,31 +60,16 @@ export type ItemType<
78
60
  * Item class that wraps a record with field accessors
79
61
  *
80
62
  * Each field is accessible as a property returning an accessor with:
81
- * - id: field identifier
82
- * - label: display label
63
+ * - label: display name
64
+ * - required: whether field is required
65
+ * - readOnly: whether field is read-only
66
+ * - defaultValue: default value
67
+ * - meta: full raw backend meta
83
68
  * - get(): get current value
84
- * - set(value): set value
69
+ * - set(value): set value (editable fields only)
85
70
  * - validate(): validate current value
86
71
  *
87
72
  * @template T - The type of the underlying data
88
- *
89
- * @example
90
- * ```typescript
91
- * const product = new AdminProduct();
92
- * const item = await product.get("product_123");
93
- *
94
- * // Field accessor access
95
- * item.Title.get() // get value
96
- * item.Title.set("New Title") // set value (editable fields only)
97
- * item.Title.meta.id // "Title"
98
- * item.Title.meta.label // "Product Title"
99
- * item.Title.meta.isEditable // true if field is editable
100
- * item.Title.validate() // validate
101
- *
102
- * // Methods
103
- * const result = item.validate(); // validate all fields
104
- * const raw = item.toJSON(); // get raw data
105
- * ```
106
73
  */
107
74
  export class Item<T extends Record<string, unknown>> {
108
75
  private _data: Partial<T>;
@@ -201,13 +168,13 @@ export class Item<T extends Record<string, unknown>> {
201
168
  const fields = this._bdo.getFields();
202
169
  const fieldDef = fields[fieldId];
203
170
 
204
- // Use field's meta directly (includes options for SelectField, reference for ReferenceField)
205
- const meta: FieldMetaType = fieldDef?.meta ?? {
206
- id: fieldId,
207
- label: fieldId,
208
- isEditable: true,
171
+ // Get raw meta from field definition
172
+ const meta: BaseFieldMetaType = fieldDef?.meta ?? {
173
+ _id: fieldId,
174
+ Name: fieldId,
175
+ Type: "String",
209
176
  };
210
- const isEditable = meta.isEditable;
177
+ const isReadOnly = fieldDef?.readOnly ?? false;
211
178
 
212
179
  // Shared validate function
213
180
  const validate = (): ValidationResultType => {
@@ -231,11 +198,15 @@ export class Item<T extends Record<string, unknown>> {
231
198
  return { valid: true, errors: [] };
232
199
  };
233
200
 
234
- // Create accessor — only add set() for editable fields
201
+ // Create accessor — only add set() for non-readOnly fields
235
202
  let accessor: FieldAccessorType<unknown>;
236
203
 
237
- if (isEditable) {
204
+ if (!isReadOnly) {
238
205
  accessor = {
206
+ label: fieldDef?.label ?? fieldId,
207
+ required: fieldDef?.required ?? false,
208
+ readOnly: false,
209
+ defaultValue: fieldDef?.defaultValue,
239
210
  meta,
240
211
  get: () => this._data[fieldId as keyof T],
241
212
  set: (value: unknown) => {
@@ -245,6 +216,10 @@ export class Item<T extends Record<string, unknown>> {
245
216
  };
246
217
  } else {
247
218
  accessor = {
219
+ label: fieldDef?.label ?? fieldId,
220
+ required: fieldDef?.required ?? false,
221
+ readOnly: true,
222
+ defaultValue: fieldDef?.defaultValue,
248
223
  meta,
249
224
  get: () => this._data[fieldId as keyof T],
250
225
  validate,
@@ -265,6 +240,9 @@ export class Item<T extends Record<string, unknown>> {
265
240
  const allValues = this.toJSON() as Record<string, unknown>;
266
241
 
267
242
  for (const [fieldId, fieldDef] of Object.entries(fields)) {
243
+ // Skip readonly fields — users can't edit them, so validation errors are confusing
244
+ if (fieldDef.readOnly) continue;
245
+
268
246
  const value = this._data[fieldId as keyof T];
269
247
 
270
248
  // 1. Type validation