@ram_28/kf-ai-sdk 1.0.20 → 1.0.21

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 (50) hide show
  1. package/dist/api/client.d.ts.map +1 -1
  2. package/dist/api/datetime.d.ts +59 -10
  3. package/dist/api/datetime.d.ts.map +1 -1
  4. package/dist/api/index.d.ts +3 -2
  5. package/dist/api/index.d.ts.map +1 -1
  6. package/dist/api.cjs +1 -1
  7. package/dist/api.d.ts +1 -1
  8. package/dist/api.d.ts.map +1 -1
  9. package/dist/api.mjs +43 -21
  10. package/dist/api.types.d.ts +2 -1
  11. package/dist/api.types.d.ts.map +1 -1
  12. package/dist/auth/AuthProvider.d.ts.map +1 -1
  13. package/dist/auth.cjs +1 -1
  14. package/dist/auth.mjs +34 -34
  15. package/dist/base-types.d.ts +1 -1
  16. package/dist/base-types.d.ts.map +1 -1
  17. package/dist/client-BIkaIr2y.js +217 -0
  18. package/dist/client-DxjRcEtN.cjs +1 -0
  19. package/dist/components/hooks/useForm/apiClient.d.ts +45 -4
  20. package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -1
  21. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
  22. package/dist/form.cjs +1 -1
  23. package/dist/form.mjs +736 -761
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/kanban.cjs +1 -1
  27. package/dist/kanban.mjs +1 -1
  28. package/dist/{metadata-0lZAfuTP.cjs → metadata-Bz8zJqC1.cjs} +1 -1
  29. package/dist/{metadata-B88D_pVS.js → metadata-VbQzyD2C.js} +1 -1
  30. package/dist/table.cjs +1 -1
  31. package/dist/table.mjs +1 -1
  32. package/dist/types/base-fields.d.ts +71 -17
  33. package/dist/types/base-fields.d.ts.map +1 -1
  34. package/dist/types/common.d.ts +0 -12
  35. package/dist/types/common.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/sdk/api/client.ts +3 -41
  38. package/sdk/api/datetime.ts +98 -14
  39. package/sdk/api/index.ts +12 -6
  40. package/sdk/api.ts +6 -3
  41. package/sdk/api.types.ts +3 -4
  42. package/sdk/auth/AuthProvider.tsx +22 -24
  43. package/sdk/base-types.ts +2 -0
  44. package/sdk/components/hooks/useForm/apiClient.ts +118 -4
  45. package/sdk/components/hooks/useForm/useForm.ts +70 -58
  46. package/sdk/index.ts +3 -0
  47. package/sdk/types/base-fields.ts +71 -17
  48. package/sdk/types/common.ts +2 -13
  49. package/dist/client-DgtkT50N.cjs +0 -1
  50. package/dist/client-V-WzUb8H.js +0 -237
package/sdk/base-types.ts CHANGED
@@ -25,6 +25,8 @@ export type {
25
25
  CurrencyValueType,
26
26
  JSONFieldType,
27
27
  ReferenceFieldType,
28
+ ExtractReferenceType,
29
+ ExtractFetchFieldType,
28
30
 
29
31
  // JSON types
30
32
  JSONValueType,
@@ -135,7 +135,62 @@ export async function submitFormData<T = any>(
135
135
  // ============================================================
136
136
 
137
137
  /**
138
- * Fetch reference field data
138
+ * Fetch reference field data using the fetchField API
139
+ * This correctly uses instance context for filter evaluation
140
+ */
141
+ export async function fetchReferenceFieldData(
142
+ boId: string,
143
+ instanceId: string,
144
+ fieldId: string
145
+ ): Promise<Array<{ Value: string; Label: string }>> {
146
+ try {
147
+ // Calls GET /{boId}/{instanceId}/field/{fieldId}/fetch
148
+ return await api(boId).fetchField(instanceId, fieldId);
149
+ } catch (error) {
150
+ console.error(`Reference data fetch error for ${fieldId}:`, error);
151
+ return [];
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Fetch all reference data for a form
157
+ * @param boId - The Business Object ID
158
+ * @param instanceId - The instance ID (draftId for create, recordId for update)
159
+ * @param referenceFieldIds - Array of reference field IDs to fetch
160
+ */
161
+ export async function fetchAllReferenceFieldData(
162
+ boId: string,
163
+ instanceId: string,
164
+ referenceFieldIds: string[]
165
+ ): Promise<Record<string, Array<{ Value: string; Label: string }>>> {
166
+ const referenceData: Record<string, Array<{ Value: string; Label: string }>> = {};
167
+
168
+ // Fetch all reference data in parallel
169
+ const fetchPromises = referenceFieldIds.map(async (fieldId) => {
170
+ try {
171
+ const data = await fetchReferenceFieldData(boId, instanceId, fieldId);
172
+ return [fieldId, data] as const;
173
+ } catch (error) {
174
+ console.warn(`Failed to fetch reference data for ${fieldId}:`, error);
175
+ return [fieldId, []] as const;
176
+ }
177
+ });
178
+
179
+ const results = await Promise.allSettled(fetchPromises);
180
+
181
+ results.forEach((result) => {
182
+ if (result.status === "fulfilled") {
183
+ const [fieldId, data] = result.value;
184
+ referenceData[fieldId] = [...data];
185
+ }
186
+ });
187
+
188
+ return referenceData;
189
+ }
190
+
191
+ /**
192
+ * @deprecated Use fetchReferenceFieldData instead
193
+ * Legacy function that fetches reference data using list() API
139
194
  */
140
195
  export async function fetchReferenceData(
141
196
  businessObject: string,
@@ -171,7 +226,8 @@ export async function fetchReferenceData(
171
226
  }
172
227
 
173
228
  /**
174
- * Fetch all reference data for a schema
229
+ * @deprecated Use fetchAllReferenceFieldData instead
230
+ * Legacy function that fetches all reference data using list() API
175
231
  */
176
232
  export async function fetchAllReferenceData(
177
233
  referenceFields: Record<string, any>
@@ -239,18 +295,26 @@ export function validateFormData<T>(
239
295
  * Clean form data before submission
240
296
  * - For create: returns all non-computed, non-undefined fields
241
297
  * - For update: returns only fields that changed from originalData
298
+ * - Transforms Reference fields to JSON format expected by backend
299
+ *
300
+ * @param data - Form data to clean
301
+ * @param computedFields - Array of computed field names to exclude
302
+ * @param operation - "create" or "update"
303
+ * @param originalData - Original data for comparison (update only)
304
+ * @param fieldTypes - Map of field names to their BDO types (e.g., { "SupplierInfo": "Reference" })
242
305
  */
243
306
  export function cleanFormData<T>(
244
307
  data: Partial<T>,
245
308
  computedFields: string[],
246
309
  operation: FormOperationType = "create",
247
- originalData?: Partial<T>
310
+ originalData?: Partial<T>,
311
+ fieldTypes?: Record<string, string>
248
312
  ): Partial<T> {
249
313
  const cleanedData: Partial<T> = {};
250
314
 
251
315
  Object.keys(data).forEach((key) => {
252
316
  const fieldKey = key as keyof T;
253
- const value = data[fieldKey];
317
+ let value = data[fieldKey];
254
318
 
255
319
  // Skip computed fields
256
320
  if (computedFields.includes(key)) {
@@ -262,6 +326,12 @@ export function cleanFormData<T>(
262
326
  return;
263
327
  }
264
328
 
329
+ // Transform Reference fields to JSON format expected by backend
330
+ // Backend expects: {"_id": "...", "_name": "..."} or a JSON string that parses to this
331
+ if (fieldTypes?.[key] === "Reference" && value !== null) {
332
+ value = transformReferenceValue(value) as any;
333
+ }
334
+
265
335
  // For create: include all non-computed, non-undefined fields
266
336
  if (operation === "create") {
267
337
  cleanedData[fieldKey] = value;
@@ -289,6 +359,50 @@ export function cleanFormData<T>(
289
359
  return cleanedData;
290
360
  }
291
361
 
362
+ /**
363
+ * Transform a Reference/Lookup field value to the format expected by backend
364
+ *
365
+ * Backend expects Reference fields as either:
366
+ * - A dict with at minimum {"_id": "..."}
367
+ * - For Lookup fields, the full referenced record can be passed
368
+ * - A JSON string that parses to this format
369
+ *
370
+ * This function handles various input formats:
371
+ * - Plain string ID: "SUPP-001" → {"_id": "SUPP-001"}
372
+ * - Object with _id: preserves ALL properties (for lookup fields with full data)
373
+ * - JSON string: '{"_id": "..."}' → parsed and validated
374
+ */
375
+ export function transformReferenceValue(value: any): Record<string, any> | null {
376
+ if (value === null || value === undefined || value === "") {
377
+ return null;
378
+ }
379
+
380
+ // Already an object with _id - preserve ALL properties (for lookup fields with full data)
381
+ if (typeof value === "object" && value._id) {
382
+ return { ...value };
383
+ }
384
+
385
+ // String value - could be plain ID or JSON string
386
+ if (typeof value === "string") {
387
+ // Try to parse as JSON first
388
+ try {
389
+ const parsed = JSON.parse(value);
390
+ if (typeof parsed === "object" && parsed._id) {
391
+ return { ...parsed };
392
+ }
393
+ // Parsed but not a valid reference object - treat original as ID
394
+ } catch {
395
+ // Not valid JSON - treat as plain ID string
396
+ }
397
+
398
+ // Plain string ID
399
+ return { _id: value };
400
+ }
401
+
402
+ // Unknown format - return as-is (let backend handle/reject it)
403
+ return value;
404
+ }
405
+
292
406
  // ============================================================
293
407
  // ERROR HANDLING
294
408
  // ============================================================
@@ -16,14 +16,14 @@ import type {
16
16
  FormFieldConfigType,
17
17
  } from "./types";
18
18
 
19
- import { processSchema, extractReferenceFields } from "./schemaParser.utils";
19
+ import { processSchema } from "./schemaParser.utils";
20
20
 
21
21
  import {
22
22
  fetchFormSchemaWithCache,
23
23
  fetchRecord,
24
24
  submitFormData,
25
- fetchAllReferenceData,
26
25
  cleanFormData,
26
+ transformReferenceValue,
27
27
  } from "./apiClient";
28
28
 
29
29
  import { api } from "../../../api";
@@ -40,7 +40,7 @@ import {
40
40
  // ============================================================
41
41
 
42
42
  export function useForm<T extends Record<string, any> = Record<string, any>>(
43
- options: UseFormOptionsType<T>
43
+ options: UseFormOptionsType<T>,
44
44
  ): UseFormReturnType<T> {
45
45
  const {
46
46
  source,
@@ -63,9 +63,13 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
63
63
  // STATE MANAGEMENT
64
64
  // ============================================================
65
65
 
66
- const [schemaConfig, setSchemaConfig] =
67
- useState<FormSchemaConfigType | null>(null);
68
- const [referenceData, setReferenceData] = useState<Record<string, any[]>>({});
66
+ const [schemaConfig, setSchemaConfig] = useState<FormSchemaConfigType | null>(
67
+ null,
68
+ );
69
+ // Reference data for cross-field validation - populated lazily by UI components if needed
70
+ const [referenceData, _setReferenceData] = useState<Record<string, any[]>>(
71
+ {},
72
+ );
69
73
  const [isSubmitting, setIsSubmitting] = useState(false);
70
74
  const [lastFormValues] = useState<Partial<T>>({});
71
75
 
@@ -176,22 +180,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
176
180
  const processed = processSchema(
177
181
  schema as any,
178
182
  {}, // Pass empty object - validation functions get live values from react-hook-form
179
- userRole
183
+ userRole,
180
184
  );
181
185
  setSchemaConfig(processed);
182
-
183
- // Fetch reference data for reference fields
184
- const refFields = extractReferenceFields(processed);
185
- if (Object.keys(refFields).length > 0) {
186
- fetchAllReferenceData(refFields)
187
- .then(setReferenceData)
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
- });
194
- }
186
+ // Reference data is fetched lazily by UI components when dropdowns are opened
187
+ // using the fetchField API with proper instance context (draftId/recordId)
195
188
  } catch (error) {
196
189
  console.error("Schema processing failed:", error);
197
190
  onSchemaErrorRef.current?.(toError(error));
@@ -219,7 +212,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
219
212
  !schemaConfig ||
220
213
  !enabled ||
221
214
  draftId ||
222
- draftCreationStartedRef.current // Prevent duplicate calls in React strict mode
215
+ draftCreationStartedRef.current // Prevent duplicate calls in React strict mode
223
216
  ) {
224
217
  return;
225
218
  }
@@ -321,7 +314,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
321
314
  const field = schemaConfig.fields[fieldName];
322
315
  if (field._bdoField.Formula) {
323
316
  const fieldDeps = getFieldDependencies(
324
- field._bdoField.Formula.ExpressionTree
317
+ field._bdoField.Formula.ExpressionTree,
325
318
  );
326
319
  fieldDeps.forEach((dep) => {
327
320
  // Only add non-computed fields as dependencies
@@ -357,10 +350,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
357
350
  // For update mode, always behave as non-interactive (only trigger for computed deps)
358
351
  // Interactive mode (create only): Always trigger draft API on blur
359
352
  // Non-interactive mode: Only trigger for computed field dependencies
360
- const shouldTrigger = (isInteractiveMode && operation !== "update")
361
- ? true // Interactive mode (create only): always trigger
362
- : (computedFieldDependencies.length > 0 &&
363
- computedFieldDependencies.includes(fieldName as Path<T>));
353
+ const shouldTrigger =
354
+ isInteractiveMode && operation !== "update"
355
+ ? true // Interactive mode (create only): always trigger
356
+ : computedFieldDependencies.length > 0 &&
357
+ computedFieldDependencies.includes(fieldName as Path<T>);
364
358
 
365
359
  if (!shouldTrigger) {
366
360
  return;
@@ -422,7 +416,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
422
416
 
423
417
  // Get computed field names to exclude from payload
424
418
  const computedFieldNames = new Set(
425
- schemaConfig.computedFields || []
419
+ schemaConfig.computedFields || [],
426
420
  );
427
421
 
428
422
  // Find fields that changed from baseline (excluding computed fields)
@@ -430,7 +424,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
430
424
  // Skip _id and computed fields
431
425
  if (key === "_id" || computedFieldNames.has(key)) return;
432
426
 
433
- const currentValue = (currentValues as any)[key];
427
+ let currentValue = (currentValues as any)[key];
434
428
  const baselineValue = (baseline as any)[key];
435
429
 
436
430
  // Include if value has changed (using JSON.stringify for deep comparison)
@@ -443,6 +437,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
443
437
  currentValue !== undefined;
444
438
 
445
439
  if (hasChanged && isNonEmpty) {
440
+ // Transform Reference fields to format expected by backend
441
+ const fieldConfig = schemaConfig.fields[key];
442
+ if (fieldConfig?._bdoField?.Type === "Reference") {
443
+ currentValue = transformReferenceValue(currentValue);
444
+ }
446
445
  (changedFields as any)[key] = currentValue;
447
446
  }
448
447
  });
@@ -470,7 +469,10 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
470
469
  let computedFieldsResponse;
471
470
  if (operation === "update" && recordId) {
472
471
  // Update mode: use draftPatch (both interactive and non-interactive)
473
- computedFieldsResponse = await client.draftPatch(recordId, payload);
472
+ computedFieldsResponse = await client.draftPatch(
473
+ recordId,
474
+ payload,
475
+ );
474
476
  } else if (isInteractiveMode && draftId) {
475
477
  // Interactive create: use draftInteraction with _id
476
478
  computedFieldsResponse = await client.draftInteraction(payload);
@@ -493,7 +495,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
493
495
  shouldValidate: false,
494
496
  });
495
497
  }
496
- }
498
+ },
497
499
  );
498
500
 
499
501
  // Update baseline with computed fields from successful API response
@@ -502,7 +504,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
502
504
  if (computedFieldNames.has(fieldName)) {
503
505
  (lastSyncedValuesRef.current as any)[fieldName] = value;
504
506
  }
505
- }
507
+ },
506
508
  );
507
509
  }
508
510
  } catch (error) {
@@ -531,7 +533,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
531
533
  computedFieldDependencies,
532
534
  isInteractiveMode,
533
535
  draftId,
534
- ]
536
+ ],
535
537
  );
536
538
 
537
539
  // ============================================================
@@ -553,18 +555,16 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
553
555
 
554
556
  // Cross-field validation
555
557
  // Transform ValidationRule[] to the format expected by validateCrossField
556
- const transformedRules = schemaConfig.crossFieldValidation.map(
557
- (rule) => ({
558
- Id: rule.Id,
559
- Condition: { ExpressionTree: rule.ExpressionTree },
560
- Message: rule.Message || `Validation failed for ${rule.Name}`,
561
- })
562
- );
558
+ const transformedRules = schemaConfig.crossFieldValidation.map((rule) => ({
559
+ Id: rule.Id,
560
+ Condition: { ExpressionTree: rule.ExpressionTree },
561
+ Message: rule.Message || `Validation failed for ${rule.Name}`,
562
+ }));
563
563
 
564
564
  const crossFieldErrors = validateCrossField(
565
565
  transformedRules,
566
566
  values as any,
567
- referenceData
567
+ referenceData,
568
568
  );
569
569
 
570
570
  if (crossFieldErrors.length > 0) {
@@ -603,11 +603,14 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
603
603
  */
604
604
  const handleSubmit = useCallback(
605
605
  (
606
- onSuccess?: (data: T, e?: React.BaseSyntheticEvent) => void | Promise<void>,
606
+ onSuccess?: (
607
+ data: T,
608
+ e?: React.BaseSyntheticEvent,
609
+ ) => void | Promise<void>,
607
610
  onError?: (
608
611
  error: import("react-hook-form").FieldErrors<T> | Error,
609
- e?: React.BaseSyntheticEvent
610
- ) => void | Promise<void>
612
+ e?: React.BaseSyntheticEvent,
613
+ ) => void | Promise<void>,
611
614
  ) => {
612
615
  return rhfForm.handleSubmit(
613
616
  // RHF onValid handler - validation passed, now do cross-field + API
@@ -627,13 +630,13 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
627
630
  Id: rule.Id,
628
631
  Condition: { ExpressionTree: rule.ExpressionTree },
629
632
  Message: rule.Message || `Validation failed for ${rule.Name}`,
630
- })
633
+ }),
631
634
  );
632
635
 
633
636
  const crossFieldErrors = validateCrossField(
634
637
  transformedRules,
635
638
  data as any,
636
- referenceData
639
+ referenceData,
637
640
  );
638
641
 
639
642
  if (crossFieldErrors.length > 0) {
@@ -649,12 +652,21 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
649
652
  return;
650
653
  }
651
654
 
655
+ // Extract field types from schema for Reference field transformation
656
+ const fieldTypes: Record<string, string> = {};
657
+ Object.entries(schemaConfig.fields).forEach(
658
+ ([fieldName, field]) => {
659
+ fieldTypes[fieldName] = field._bdoField.Type;
660
+ },
661
+ );
662
+
652
663
  // Clean data for submission
653
664
  const cleanedData = cleanFormData(
654
665
  data as any,
655
666
  schemaConfig.computedFields,
656
667
  operation,
657
- recordData as Partial<T> | undefined
668
+ recordData as Partial<T> | undefined,
669
+ fieldTypes,
658
670
  );
659
671
 
660
672
  let result;
@@ -667,7 +679,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
667
679
  // Interactive create: must have draftId
668
680
  if (!draftId) {
669
681
  throw new Error(
670
- "Interactive create mode requires a draft ID. Draft creation may have failed."
682
+ "Interactive create mode requires a draft ID. Draft creation may have failed.",
671
683
  );
672
684
  }
673
685
  // POST /{bdo_id}/draft with _id in payload
@@ -687,7 +699,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
687
699
  source,
688
700
  operation,
689
701
  cleanedData,
690
- recordId
702
+ recordId,
691
703
  );
692
704
 
693
705
  if (!result.success) {
@@ -716,7 +728,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
716
728
  // RHF onInvalid handler - validation failed
717
729
  (errors, event) => {
718
730
  onError?.(errors, event);
719
- }
731
+ },
720
732
  );
721
733
  },
722
734
  [
@@ -729,7 +741,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
729
741
  recordData,
730
742
  isInteractiveMode,
731
743
  draftId,
732
- ]
744
+ ],
733
745
  );
734
746
 
735
747
  // ============================================================
@@ -740,7 +752,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
740
752
  <K extends keyof T>(fieldName: K): FormFieldConfigType | null => {
741
753
  return schemaConfig?.fields[fieldName as string] || null;
742
754
  },
743
- [schemaConfig]
755
+ [schemaConfig],
744
756
  );
745
757
 
746
758
  const getFields = useCallback((): Record<keyof T, FormFieldConfigType> => {
@@ -758,7 +770,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
758
770
  <K extends keyof T>(fieldName: K): boolean => {
759
771
  return !!schemaConfig?.fields[fieldName as string];
760
772
  },
761
- [schemaConfig]
773
+ [schemaConfig],
762
774
  );
763
775
 
764
776
  const isFieldRequired = useCallback(
@@ -767,7 +779,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
767
779
  schemaConfig?.requiredFields.includes(fieldName as string) || false
768
780
  );
769
781
  },
770
- [schemaConfig]
782
+ [schemaConfig],
771
783
  );
772
784
 
773
785
  const isFieldComputed = useCallback(
@@ -776,7 +788,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
776
788
  schemaConfig?.computedFields.includes(fieldName as string) || false
777
789
  );
778
790
  },
779
- [schemaConfig]
791
+ [schemaConfig],
780
792
  );
781
793
 
782
794
  // ============================================================
@@ -806,12 +818,12 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
806
818
 
807
819
  const computedFields = useMemo<Array<keyof T>>(
808
820
  () => (schemaConfig?.computedFields as Array<keyof T>) || [],
809
- [schemaConfig]
821
+ [schemaConfig],
810
822
  );
811
823
 
812
824
  const requiredFields = useMemo<Array<keyof T>>(
813
825
  () => (schemaConfig?.requiredFields as Array<keyof T>) || [],
814
- [schemaConfig]
826
+ [schemaConfig],
815
827
  );
816
828
 
817
829
  // ============================================================
@@ -864,7 +876,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
864
876
  value,
865
877
  [rule],
866
878
  currentValues,
867
- lastFormValues as T | undefined
879
+ lastFormValues as T | undefined,
868
880
  );
869
881
  if (!result.isValid) {
870
882
  return result.message || rule.Message || "Invalid value";
@@ -936,7 +948,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
936
948
  onBlur: enhancedOnBlur,
937
949
  });
938
950
  },
939
- [rhfForm, validationRules, triggerComputationAfterValidation, mode]
951
+ [rhfForm, validationRules, triggerComputationAfterValidation, mode],
940
952
  );
941
953
 
942
954
  return {
package/sdk/index.ts CHANGED
@@ -92,6 +92,9 @@ export type {
92
92
  LookupFieldType,
93
93
  ArrayFieldType,
94
94
  JSONFieldType,
95
+ ReferenceFieldType,
96
+ ExtractReferenceType,
97
+ ExtractFetchFieldType,
95
98
  } from './types/base-fields';
96
99
 
97
100
  // ============================================================
@@ -67,27 +67,47 @@ export type LongFieldType = number;
67
67
  */
68
68
  export type BooleanFieldType = boolean;
69
69
 
70
+ /**
71
+ * Encoded date format from API response
72
+ * API returns: { "$__d__": "YYYY-MM-DD" }
73
+ */
74
+ export interface DateEncodedType {
75
+ $__d__: string;
76
+ }
77
+
78
+ /**
79
+ * Encoded datetime format from API response
80
+ * API returns: { "$__dt__": unix_timestamp_seconds }
81
+ */
82
+ export interface DateTimeEncodedType {
83
+ $__dt__: number;
84
+ }
85
+
70
86
  /**
71
87
  * Date field (date only, no time)
72
- * Resolves to: Date
88
+ * API Response Format: { "$__d__": "YYYY-MM-DD" }
89
+ * API Request Format: "YYYY-MM-DD"
73
90
  * Storage: DATE in database
74
91
  * Use this for birth dates, due dates, calendar events
75
92
  *
76
93
  * @example
77
- * DateFieldType // => Date (date only)
94
+ * // Response from API:
95
+ * { "$__d__": "2025-03-15" }
78
96
  */
79
- export type DateFieldType = Date;
97
+ export type DateFieldType = DateEncodedType;
80
98
 
81
99
  /**
82
100
  * DateTime field (date and time)
83
- * Resolves to: Date
101
+ * API Response Format: { "$__dt__": unix_timestamp_seconds }
102
+ * API Request Format: "YYYY-MM-DD HH:MM:SS"
84
103
  * Storage: DATETIME/TIMESTAMP in database
85
104
  * Use this for created_at, updated_at, event timestamps
86
105
  *
87
106
  * @example
88
- * DateTimeFieldType // => Date (with time)
107
+ * // Response from API:
108
+ * { "$__dt__": 1769110463 }
89
109
  */
90
- export type DateTimeFieldType = Date;
110
+ export type DateTimeFieldType = DateTimeEncodedType;
91
111
 
92
112
  // ============================================================
93
113
  // COMPLEX FIELD TYPES
@@ -124,7 +144,47 @@ export type CurrencyValueType =
124
144
  */
125
145
  export type JSONFieldType<T = JSONValueType> = T;
126
146
 
127
- export type ReferenceFieldType = string;
147
+ /**
148
+ * Reference/Lookup field for relationships to other Business Data Objects
149
+ *
150
+ * @template TReferencedType - The full type of the referenced BDO record
151
+ *
152
+ * Runtime behavior:
153
+ * - The field stores the full referenced record (e.g., full supplier object)
154
+ * - API returns the complete record from the referenced BDO
155
+ * - On save, the full object is sent in the payload
156
+ *
157
+ * @example
158
+ * SupplierInfo: ReferenceFieldType<BaseSupplierType>;
159
+ * // At runtime: { _id: "...", SupplierId: "...", SupplierName: "...", ... }
160
+ */
161
+ export type ReferenceFieldType<TReferencedType = unknown> = TReferencedType;
162
+
163
+ /**
164
+ * Extract the referenced type from a ReferenceFieldType
165
+ * Note: Since ReferenceFieldType<T> = T, this just returns T if it has _id
166
+ */
167
+ export type ExtractReferenceType<T> = T extends { _id: string } ? T : never;
168
+
169
+ /**
170
+ * Extract the type that fetchField should return for a given field type
171
+ * - For Reference fields (objects with _id) → the full referenced record type
172
+ * - For other field types → { Value: string; Label: string } (static dropdown)
173
+ *
174
+ * Detection: All BDO types have _id: string, so we check for that property
175
+ * to distinguish reference fields from primitive fields like StringFieldType.
176
+ *
177
+ * @example
178
+ * type SupplierOptions = ExtractFetchFieldType<BaseProductType["SupplierInfo"]>;
179
+ * // Result: BaseSupplierType (has _id)
180
+ *
181
+ * type CategoryOptions = ExtractFetchFieldType<BaseProductType["Category"]>;
182
+ * // Result: { Value: string; Label: string } (string has no _id)
183
+ */
184
+ export type ExtractFetchFieldType<T> =
185
+ T extends { _id: string }
186
+ ? T
187
+ : { Value: string; Label: string };
128
188
 
129
189
  /**
130
190
  * Valid JSON value types
@@ -156,17 +216,11 @@ export interface JSONArrayType extends Array<JSONValueType> {}
156
216
  export type SelectFieldType<T extends string> = T;
157
217
 
158
218
  /**
159
- * Lookup field for references to other records
160
- * @template T - Type of the referenced record ID (defaults to string)
161
- * Resolves to: T
162
- * Storage: VARCHAR in database (stores the ID)
163
- * Use this for foreign keys, relationships, references
164
- *
165
- * @example
166
- * LookupFieldType // => string (referenced record ID)
167
- * LookupFieldType<IdFieldType> // => string (typed as IdFieldType)
219
+ * Alias for ReferenceFieldType (Lookup = Reference in the backend)
220
+ * @template TReferencedType - The full type of the referenced BDO record
221
+ * @deprecated Use ReferenceFieldType instead
168
222
  */
169
- export type LookupFieldType<T extends string = string> = T;
223
+ export type LookupFieldType<TReferencedType = unknown> = ReferenceFieldType<TReferencedType>;
170
224
 
171
225
  // ============================================================
172
226
  // CONTAINER AND UTILITY TYPES
@@ -71,19 +71,8 @@ export interface ConditionGroupType<T = any> {
71
71
  */
72
72
  export type FilterType<T = any> = ConditionGroupType<T>;
73
73
 
74
- /**
75
- * DateTime encoding format used by the API
76
- */
77
- export interface DateTimeEncodedType {
78
- $__dt__: number;
79
- }
80
-
81
- /**
82
- * Date encoding format used by the API
83
- */
84
- export interface DateEncodedType {
85
- $__d__: string;
86
- }
74
+ // Note: DateTimeEncodedType and DateEncodedType are now defined in base-fields.ts
75
+ // They are re-exported from api/index.ts for convenience
87
76
 
88
77
  /**
89
78
  * Standard paginated list response