@ram_28/kf-ai-sdk 2.0.14 → 2.0.16

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 (157) hide show
  1. package/README.md +10 -9
  2. package/dist/FileField-BWrSHNRq.js +296 -0
  3. package/dist/FileField-eDeuzln8.cjs +1 -0
  4. package/dist/api.cjs +1 -1
  5. package/dist/api.mjs +2 -2
  6. package/dist/auth.cjs +1 -1
  7. package/dist/auth.mjs +1 -1
  8. package/dist/bdo/core/BaseBdo.d.ts +1 -1
  9. package/dist/bdo.cjs +1 -1
  10. package/dist/bdo.mjs +230 -474
  11. package/dist/{client-DnO2KKrw.cjs → client-D5k4SYuw.cjs} +1 -1
  12. package/dist/{client-iQTqFDNI.js → client-_ayziI1d.js} +33 -32
  13. package/dist/components/hooks/index.d.ts +9 -3
  14. package/dist/components/hooks/index.d.ts.map +1 -1
  15. package/dist/{workflow/components → components/hooks}/useActivityForm/createActivityItemProxy.d.ts +9 -5
  16. package/dist/components/hooks/useActivityForm/createActivityItemProxy.d.ts.map +1 -0
  17. package/dist/components/hooks/useActivityForm/createActivityResolver.d.ts +23 -0
  18. package/dist/components/hooks/useActivityForm/createActivityResolver.d.ts.map +1 -0
  19. package/dist/components/hooks/useActivityForm/index.d.ts.map +1 -0
  20. package/dist/{workflow/components → components/hooks}/useActivityForm/types.d.ts +12 -8
  21. package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -0
  22. package/dist/{workflow/components → components/hooks}/useActivityForm/useActivityForm.d.ts +2 -2
  23. package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -0
  24. package/dist/components/hooks/useActivityTable/index.d.ts +4 -0
  25. package/dist/components/hooks/useActivityTable/index.d.ts.map +1 -0
  26. package/dist/components/hooks/useActivityTable/types.d.ts +36 -0
  27. package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -0
  28. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts +4 -0
  29. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -0
  30. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts.map +1 -0
  31. package/dist/components/hooks/useBDOForm/createResolver.d.ts.map +1 -0
  32. package/dist/components/hooks/useBDOForm/index.d.ts +6 -0
  33. package/dist/components/hooks/useBDOForm/index.d.ts.map +1 -0
  34. package/dist/components/hooks/useBDOForm/shared.d.ts +50 -0
  35. package/dist/components/hooks/useBDOForm/shared.d.ts.map +1 -0
  36. package/dist/components/hooks/{useForm → useBDOForm}/types.d.ts +6 -6
  37. package/dist/components/hooks/useBDOForm/types.d.ts.map +1 -0
  38. package/dist/components/hooks/{useForm/useForm.d.ts → useBDOForm/useBDOForm.d.ts} +4 -4
  39. package/dist/components/hooks/useBDOForm/useBDOForm.d.ts.map +1 -0
  40. package/dist/components/hooks/useBDOTable/index.d.ts +3 -0
  41. package/dist/components/hooks/useBDOTable/index.d.ts.map +1 -0
  42. package/dist/components/hooks/useBDOTable/types.d.ts +24 -0
  43. package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -0
  44. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +3 -0
  45. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -0
  46. package/dist/components/hooks/useTable/index.d.ts +2 -2
  47. package/dist/components/hooks/useTable/index.d.ts.map +1 -1
  48. package/dist/components/hooks/useTable/types.d.ts +11 -10
  49. package/dist/components/hooks/useTable/types.d.ts.map +1 -1
  50. package/dist/components/hooks/useTable/useTable.d.ts +1 -1
  51. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
  52. package/dist/form.cjs +1 -1
  53. package/dist/form.d.ts +1 -1
  54. package/dist/form.d.ts.map +1 -1
  55. package/dist/form.mjs +279 -344
  56. package/dist/form.types.d.ts +1 -1
  57. package/dist/form.types.d.ts.map +1 -1
  58. package/dist/{metadata-DpfI3zRN.js → metadata-Cc1mBcLS.js} +1 -1
  59. package/dist/{metadata-DgLSJkF5.cjs → metadata-DWXQPDav.cjs} +1 -1
  60. package/dist/shared-5a7UkED1.js +1180 -0
  61. package/dist/shared-nnmlRVs7.cjs +1 -0
  62. package/dist/table.cjs +1 -1
  63. package/dist/table.d.ts +1 -0
  64. package/dist/table.d.ts.map +1 -1
  65. package/dist/table.mjs +17 -192
  66. package/dist/table.types.d.ts +2 -1
  67. package/dist/table.types.d.ts.map +1 -1
  68. package/dist/types/base-fields.d.ts +4 -4
  69. package/dist/types/base-fields.d.ts.map +1 -1
  70. package/dist/types/constants.d.ts +3 -3
  71. package/dist/useTable-CeRklbdT.cjs +1 -0
  72. package/dist/useTable-DS0-WInw.js +203 -0
  73. package/dist/workflow/Activity.d.ts +19 -7
  74. package/dist/workflow/Activity.d.ts.map +1 -1
  75. package/dist/workflow/client.d.ts +2 -2
  76. package/dist/workflow/client.d.ts.map +1 -1
  77. package/dist/workflow/createFieldFromMeta.d.ts +29 -0
  78. package/dist/workflow/createFieldFromMeta.d.ts.map +1 -0
  79. package/dist/workflow/index.d.ts +1 -2
  80. package/dist/workflow/index.d.ts.map +1 -1
  81. package/dist/workflow/types.d.ts +16 -12
  82. package/dist/workflow/types.d.ts.map +1 -1
  83. package/dist/workflow.cjs +1 -1
  84. package/dist/workflow.d.ts +5 -2
  85. package/dist/workflow.d.ts.map +1 -1
  86. package/dist/workflow.mjs +687 -352
  87. package/dist/workflow.types.d.ts +1 -0
  88. package/dist/workflow.types.d.ts.map +1 -1
  89. package/docs/bdo.md +1 -1
  90. package/docs/gaps.md +360 -0
  91. package/docs/useActivityForm.md +393 -0
  92. package/docs/useActivityTable.md +418 -0
  93. package/docs/{useForm.md → useBDOForm.md} +24 -24
  94. package/docs/useBDOTable.md +284 -0
  95. package/docs/workflow.md +148 -297
  96. package/package.json +2 -2
  97. package/sdk/bdo/core/BaseBdo.ts +2 -2
  98. package/sdk/bdo/fields/UserField.ts +1 -1
  99. package/sdk/components/hooks/index.ts +28 -5
  100. package/sdk/components/hooks/useActivityForm/createActivityItemProxy.ts +400 -0
  101. package/sdk/components/hooks/useActivityForm/createActivityResolver.ts +87 -0
  102. package/sdk/{workflow/components → components/hooks}/useActivityForm/types.ts +24 -11
  103. package/sdk/components/hooks/useActivityForm/useActivityForm.ts +478 -0
  104. package/sdk/components/hooks/useActivityTable/index.ts +8 -0
  105. package/sdk/components/hooks/useActivityTable/types.ts +47 -0
  106. package/sdk/components/hooks/useActivityTable/useActivityTable.ts +40 -0
  107. package/sdk/components/hooks/{useForm → useBDOForm}/index.ts +4 -3
  108. package/sdk/components/hooks/useBDOForm/shared.ts +250 -0
  109. package/sdk/components/hooks/{useForm → useBDOForm}/types.ts +9 -9
  110. package/sdk/components/hooks/{useForm/useForm.ts → useBDOForm/useBDOForm.ts} +70 -96
  111. package/sdk/components/hooks/useBDOTable/index.ts +2 -0
  112. package/sdk/components/hooks/useBDOTable/types.ts +22 -0
  113. package/sdk/components/hooks/useBDOTable/useBDOTable.ts +16 -0
  114. package/sdk/components/hooks/useTable/index.ts +3 -3
  115. package/sdk/components/hooks/useTable/types.ts +16 -12
  116. package/sdk/components/hooks/useTable/useTable.ts +56 -49
  117. package/sdk/form.ts +2 -2
  118. package/sdk/form.types.ts +4 -4
  119. package/sdk/table.ts +4 -1
  120. package/sdk/table.types.ts +7 -4
  121. package/sdk/types/base-fields.ts +4 -4
  122. package/sdk/types/constants.ts +3 -3
  123. package/sdk/workflow/Activity.ts +36 -12
  124. package/sdk/workflow/client.ts +65 -12
  125. package/sdk/workflow/createFieldFromMeta.ts +110 -0
  126. package/sdk/workflow/index.ts +1 -6
  127. package/sdk/workflow/types.ts +20 -11
  128. package/sdk/workflow.ts +11 -2
  129. package/sdk/workflow.types.ts +7 -0
  130. package/dist/BaseField-B6da88U7.js +0 -40
  131. package/dist/BaseField-Drp0-OxL.cjs +0 -1
  132. package/dist/components/hooks/useForm/createItemProxy.d.ts.map +0 -1
  133. package/dist/components/hooks/useForm/createResolver.d.ts.map +0 -1
  134. package/dist/components/hooks/useForm/index.d.ts +0 -5
  135. package/dist/components/hooks/useForm/index.d.ts.map +0 -1
  136. package/dist/components/hooks/useForm/types.d.ts.map +0 -1
  137. package/dist/components/hooks/useForm/useForm.d.ts.map +0 -1
  138. package/dist/error-handling-CAoD0Kwb.cjs +0 -1
  139. package/dist/error-handling-CrhTtD88.js +0 -14
  140. package/dist/index.esm-Cj63v5ny.js +0 -1014
  141. package/dist/index.esm-DuwT11sx.cjs +0 -1
  142. package/dist/workflow/components/useActivityForm/createActivityItemProxy.d.ts.map +0 -1
  143. package/dist/workflow/components/useActivityForm/createActivityResolver.d.ts +0 -22
  144. package/dist/workflow/components/useActivityForm/createActivityResolver.d.ts.map +0 -1
  145. package/dist/workflow/components/useActivityForm/index.d.ts.map +0 -1
  146. package/dist/workflow/components/useActivityForm/types.d.ts.map +0 -1
  147. package/dist/workflow/components/useActivityForm/useActivityForm.d.ts.map +0 -1
  148. package/docs/useTable.md +0 -369
  149. package/sdk/workflow/components/useActivityForm/createActivityItemProxy.ts +0 -130
  150. package/sdk/workflow/components/useActivityForm/createActivityResolver.ts +0 -61
  151. package/sdk/workflow/components/useActivityForm/useActivityForm.ts +0 -386
  152. /package/dist/{workflow/components → components/hooks}/useActivityForm/index.d.ts +0 -0
  153. /package/dist/components/hooks/{useForm → useBDOForm}/createItemProxy.d.ts +0 -0
  154. /package/dist/components/hooks/{useForm → useBDOForm}/createResolver.d.ts +0 -0
  155. /package/sdk/{workflow/components → components/hooks}/useActivityForm/index.ts +0 -0
  156. /package/sdk/components/hooks/{useForm → useBDOForm}/createItemProxy.ts +0 -0
  157. /package/sdk/components/hooks/{useForm → useBDOForm}/createResolver.ts +0 -0
@@ -0,0 +1,250 @@
1
+ // ============================================================
2
+ // SHARED FORM UTILITIES
3
+ // ============================================================
4
+ // Coercion functions shared between useBDOForm and useActivityForm.
5
+
6
+ import type {
7
+ UseFormReturn,
8
+ Control,
9
+ Path,
10
+ FieldValues,
11
+ } from 'react-hook-form';
12
+ import type { MutableRefObject } from 'react';
13
+ import type { BaseField } from '../../../bdo/fields/BaseField';
14
+
15
+ /** Coerce form value to match field's expected type (HTML inputs return strings) */
16
+ export function coerceFieldValue(
17
+ field: BaseField<unknown>,
18
+ value: unknown,
19
+ ): unknown {
20
+ const type = field.meta.Type;
21
+ if (typeof value === 'string' && type === 'Number') {
22
+ return value === '' ? undefined : Number(value);
23
+ }
24
+ // Date/DateTime: empty string → undefined (don't send to backend)
25
+ if (
26
+ typeof value === 'string' &&
27
+ value === '' &&
28
+ (type === 'Date' || type === 'DateTime')
29
+ ) {
30
+ return undefined;
31
+ }
32
+ // DateTime: normalize to HH:MM:SS and ensure Z suffix for API request format
33
+ if (typeof value === 'string' && value !== '' && type === 'DateTime') {
34
+ let normalized = value;
35
+ if (normalized.endsWith('Z')) normalized = normalized.slice(0, -1);
36
+ // HTML datetime-local may omit seconds (e.g. "2026-02-18T15:12")
37
+ const timePart = normalized.split('T')[1] || '';
38
+ if ((timePart.match(/:/g) || []).length === 1) {
39
+ normalized += ':00';
40
+ }
41
+ return normalized + 'Z';
42
+ }
43
+ return value;
44
+ }
45
+
46
+ /**
47
+ * Strip trailing Z from DateTime response values for HTML datetime-local inputs.
48
+ * Takes a fields map (use bdo.getFields() for BDO forms).
49
+ */
50
+ export function coerceRecordForForm(
51
+ fields: Record<string, BaseField<unknown>>,
52
+ data: Record<string, unknown>,
53
+ ): Record<string, unknown> {
54
+ const result = { ...data };
55
+ for (const [key, value] of Object.entries(result)) {
56
+ if (
57
+ typeof value === 'string' &&
58
+ fields[key]?.meta.Type === 'DateTime' &&
59
+ value.endsWith('Z')
60
+ ) {
61
+ result[key] = value.slice(0, -1);
62
+ }
63
+ }
64
+ return result;
65
+ }
66
+
67
+ // ============================================================
68
+ // SYNC UTILITIES
69
+ // ============================================================
70
+ // Shared per-field sync pattern used by useBDOForm and useActivityForm.
71
+ // createSyncField → validate → coerce → API call → reset dirty → update readonly
72
+ // createEnhancedRegister → inject syncField into register's onBlur/onChange
73
+ // createEnhancedControl → inject syncField into Controller's control.register
74
+
75
+ /** API function signature for per-field sync */
76
+ export type SyncApiFnType = (
77
+ fieldName: string,
78
+ value: unknown,
79
+ ) => Promise<unknown>;
80
+
81
+ export interface CreateSyncFieldOptionsType {
82
+ apiFn: SyncApiFnType;
83
+ allFields: Record<string, BaseField<unknown>>;
84
+ readonlyFieldNames: string[];
85
+ rhf: UseFormReturn;
86
+ isComputingRef: MutableRefObject<boolean>;
87
+ }
88
+
89
+ /**
90
+ * Factory that returns a `syncField(fieldName)` function.
91
+ * Validates the field, coerces its value, sends it to the API,
92
+ * resets dirty state, and updates computed/readonly fields from the response.
93
+ */
94
+ export function createSyncField(
95
+ opts: CreateSyncFieldOptionsType,
96
+ ): (fieldName: string) => Promise<void> {
97
+ const { apiFn, allFields, readonlyFieldNames, rhf, isComputingRef } = opts;
98
+
99
+ return async (fieldName: string) => {
100
+ if (isComputingRef.current) return;
101
+ isComputingRef.current = true;
102
+
103
+ try {
104
+ const isValid = await rhf.trigger(fieldName as Path<FieldValues>);
105
+ if (!isValid) return;
106
+
107
+ const rawValue = rhf.getValues(fieldName as any);
108
+ const field = allFields[fieldName];
109
+ const value = field ? coerceFieldValue(field, rawValue) : rawValue;
110
+
111
+ const response = await apiFn(fieldName, value);
112
+
113
+ // If apiFn chose not to sync (e.g. draft not ready), skip cleanup
114
+ if (response === undefined) return;
115
+
116
+ // Field saved — reset dirty state so it's not re-sent on submit
117
+ rhf.resetField(fieldName as Path<FieldValues>, {
118
+ defaultValue: rawValue,
119
+ keepTouched: true,
120
+ keepError: true,
121
+ } as any);
122
+
123
+ // Update computed/readonly fields from response
124
+ if (response && typeof response === 'object') {
125
+ const responseData =
126
+ (response as any).Data ?? (response as any);
127
+ if (responseData && typeof responseData === 'object') {
128
+ const readonlySet = new Set(readonlyFieldNames);
129
+ for (const key of Object.keys(responseData)) {
130
+ if (readonlySet.has(key) && responseData[key] !== undefined) {
131
+ const current = rhf.getValues(key as any);
132
+ if (current !== responseData[key]) {
133
+ rhf.setValue(
134
+ key as Path<FieldValues>,
135
+ responseData[key] as any,
136
+ { shouldDirty: false, shouldValidate: false },
137
+ );
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ } catch (error) {
144
+ console.warn('syncField failed:', error);
145
+ } finally {
146
+ isComputingRef.current = false;
147
+ }
148
+ };
149
+ }
150
+
151
+ export interface CreateEnhancedRegisterOptionsType {
152
+ rhf: UseFormReturn;
153
+ allFields: Record<string, BaseField<unknown>>;
154
+ syncField: (fieldName: string) => Promise<void>;
155
+ syncOnBlur: boolean;
156
+ syncOnChange: boolean;
157
+ }
158
+
159
+ /**
160
+ * Factory that returns an enhanced `register` function.
161
+ * Injects syncField into onBlur/onChange based on mode, and auto-disables readonly fields.
162
+ */
163
+ export function createEnhancedRegister(
164
+ opts: CreateEnhancedRegisterOptionsType,
165
+ ) {
166
+ const { rhf, allFields, syncField, syncOnBlur, syncOnChange } = opts;
167
+
168
+ return (name: string, registerOptions?: any) => {
169
+ const field = allFields[name];
170
+ const isReadonly = field ? field.readOnly : false;
171
+
172
+ const result = rhf.register(name as Path<FieldValues>, {
173
+ ...registerOptions,
174
+ ...(syncOnBlur
175
+ ? {
176
+ onBlur: async (e: any) => {
177
+ await registerOptions?.onBlur?.(e);
178
+ await syncField(name);
179
+ },
180
+ }
181
+ : {}),
182
+ ...(syncOnChange
183
+ ? {
184
+ onChange: async (e: any) => {
185
+ await registerOptions?.onChange?.(e);
186
+ await syncField(name);
187
+ },
188
+ }
189
+ : {}),
190
+ ...(isReadonly ? { disabled: true } : {}),
191
+ });
192
+
193
+ if (isReadonly) {
194
+ return { ...result, disabled: true as const };
195
+ }
196
+
197
+ return result;
198
+ };
199
+ }
200
+
201
+ export interface CreateEnhancedControlOptionsType {
202
+ control: Control;
203
+ syncField: (fieldName: string) => Promise<void>;
204
+ syncOnBlur: boolean;
205
+ syncOnChange: boolean;
206
+ }
207
+
208
+ /**
209
+ * Factory that returns a Proxy over RHF's `control` object.
210
+ * Intercepts `control.register` to inject syncField into onChange/onBlur
211
+ * for Controller components.
212
+ */
213
+ export function createEnhancedControl(
214
+ opts: CreateEnhancedControlOptionsType,
215
+ ): Control {
216
+ const { control, syncField, syncOnBlur, syncOnChange } = opts;
217
+
218
+ return new Proxy(control, {
219
+ get(target, prop, receiver) {
220
+ if (prop === 'register') {
221
+ return (name: string, options?: any) => {
222
+ const result = target.register(name as any, options);
223
+ const originalOnChange = result.onChange;
224
+ const originalOnBlur = result.onBlur;
225
+
226
+ return {
227
+ ...result,
228
+ ...(syncOnChange
229
+ ? {
230
+ onChange: async (event: any) => {
231
+ await originalOnChange(event);
232
+ await syncField(name);
233
+ },
234
+ }
235
+ : {}),
236
+ ...(syncOnBlur
237
+ ? {
238
+ onBlur: async (event: any) => {
239
+ await originalOnBlur(event);
240
+ await syncField(name);
241
+ },
242
+ }
243
+ : {}),
244
+ };
245
+ };
246
+ }
247
+ return Reflect.get(target, prop, receiver);
248
+ },
249
+ });
250
+ }
@@ -33,7 +33,7 @@ export type AllFieldsType<B> = ExtractEditableType<B> & ExtractReadonlyType<B> &
33
33
  // BDO CAPABILITY CONSTRAINTS
34
34
  // ============================================================
35
35
 
36
- /** Minimum BDO shape required for any useForm usage */
36
+ /** Minimum BDO shape required for any useBDOForm usage */
37
37
  interface BaseBdoShape {
38
38
  readonly meta: BdoMetaType;
39
39
  getFields(): Record<string, BaseField<unknown>>;
@@ -81,13 +81,13 @@ export type HandleSubmitType<TRead = unknown> = (
81
81
  // OPTIONS TYPE
82
82
  // ============================================================
83
83
 
84
- export type UseFormOptionsType<B extends BaseBdo<any, any, any>> =
85
- | UseFormCreateOptionsType<B>
86
- | UseFormUpdateOptionsType<B>
87
- | UseFormAutoOptionsType<B>;
84
+ export type UseBDOFormOptionsType<B extends BaseBdo<any, any, any>> =
85
+ | UseBDOFormCreateOptionsType<B>
86
+ | UseBDOFormUpdateOptionsType<B>
87
+ | UseBDOFormAutoOptionsType<B>;
88
88
 
89
89
  /** Options when operation is explicitly "create" — BDO must have create() */
90
- interface UseFormCreateOptionsType<B extends BaseBdo<any, any, any>> {
90
+ interface UseBDOFormCreateOptionsType<B extends BaseBdo<any, any, any>> {
91
91
  bdo: B & CreatableBdo<ExtractEditableType<B>>;
92
92
  operation: "create";
93
93
  recordId?: undefined;
@@ -99,7 +99,7 @@ interface UseFormCreateOptionsType<B extends BaseBdo<any, any, any>> {
99
99
  }
100
100
 
101
101
  /** Options when operation is explicitly "update" — BDO must have get() + update() */
102
- interface UseFormUpdateOptionsType<B extends BaseBdo<any, any, any>> {
102
+ interface UseBDOFormUpdateOptionsType<B extends BaseBdo<any, any, any>> {
103
103
  bdo: B & UpdatableBdo<ExtractEditableType<B>>;
104
104
  operation: "update";
105
105
  recordId: string;
@@ -111,7 +111,7 @@ interface UseFormUpdateOptionsType<B extends BaseBdo<any, any, any>> {
111
111
  }
112
112
 
113
113
  /** Options when operation is auto-inferred — BDO must support both */
114
- interface UseFormAutoOptionsType<B extends BaseBdo<any, any, any>> {
114
+ interface UseBDOFormAutoOptionsType<B extends BaseBdo<any, any, any>> {
115
115
  bdo: B & FormBdo<ExtractEditableType<B>>;
116
116
  operation?: undefined;
117
117
  recordId?: string;
@@ -187,7 +187,7 @@ export type FormItemType<
187
187
  // RETURN TYPE
188
188
  // ============================================================
189
189
 
190
- export interface UseFormReturnType<B extends BaseBdo<any, any, any>> {
190
+ export interface UseBDOFormReturnType<B extends BaseBdo<any, any, any>> {
191
191
  // Item with typed accessors
192
192
  item: FormItemType<ExtractEditableType<B>, ExtractReadonlyType<B>>;
193
193
 
@@ -4,61 +4,30 @@ import {
4
4
  type FieldValues,
5
5
  type FieldErrors,
6
6
  type Control,
7
- type RegisterOptions,
8
7
  type UseFormReturn as RHFUseFormReturn,
9
8
  } from "react-hook-form";
10
9
  import { useQuery } from "@tanstack/react-query";
11
10
  import { createResolver } from "./createResolver";
12
11
  import { createItemProxy } from "./createItemProxy";
12
+ import {
13
+ coerceFieldValue,
14
+ coerceRecordForForm,
15
+ createSyncField,
16
+ createEnhancedRegister,
17
+ createEnhancedControl,
18
+ } from "./shared";
13
19
  import { getBdoSchema } from "../../../api/metadata";
14
20
  import { api } from "../../../api/client";
15
21
  import type { BaseBdo } from "../../../bdo";
16
22
  import type { CreateUpdateResponseType } from "../../../types/common";
17
- import type { BaseField } from "../../../bdo/fields/BaseField";
18
23
  import type {
19
- UseFormOptionsType,
20
- UseFormReturnType,
24
+ UseBDOFormOptionsType,
25
+ UseBDOFormReturnType,
21
26
  HandleSubmitType,
22
27
  AllFieldsType,
23
28
  UpdatableBdo,
24
29
  } from "./types";
25
30
 
26
- /** Coerce form value to match field's expected type (HTML inputs return strings) */
27
- function coerceFieldValue(field: BaseField<unknown>, value: unknown): unknown {
28
- const type = field.meta.Type;
29
- if (typeof value === "string" && type === "Number") {
30
- return value === "" ? undefined : Number(value);
31
- }
32
- // Date/DateTime: empty string → undefined (don't send to backend)
33
- if (typeof value === "string" && value === "" && (type === "Date" || type === "DateTime")) {
34
- return undefined;
35
- }
36
- // DateTime: normalize to HH:MM:SS and ensure Z suffix for API request format
37
- if (typeof value === "string" && value !== "" && type === "DateTime") {
38
- let normalized = value;
39
- if (normalized.endsWith("Z")) normalized = normalized.slice(0, -1);
40
- // HTML datetime-local may omit seconds (e.g. "2026-02-18T15:12")
41
- const timePart = normalized.split("T")[1] || "";
42
- if ((timePart.match(/:/g) || []).length === 1) {
43
- normalized += ":00";
44
- }
45
- return normalized + "Z";
46
- }
47
- return value;
48
- }
49
-
50
- /** Strip trailing Z from DateTime response values for HTML datetime-local inputs */
51
- function coerceRecordForForm(bdo: BaseBdo<any, any, any>, data: Record<string, unknown>): Record<string, unknown> {
52
- const fields = bdo.getFields();
53
- const result = { ...data };
54
- for (const [key, value] of Object.entries(result)) {
55
- if (typeof value === "string" && fields[key]?.meta.Type === "DateTime" && value.endsWith("Z")) {
56
- result[key] = value.slice(0, -1);
57
- }
58
- }
59
- return result;
60
- }
61
-
62
31
  /**
63
32
  * A form hook that integrates with React Hook Form.
64
33
  *
@@ -71,11 +40,11 @@ function coerceRecordForForm(bdo: BaseBdo<any, any, any>, data: Record<string, u
71
40
  * - Smart register: auto-disables readonly fields
72
41
  * - Payload filtering: handleSubmit auto-filters to editable fields only
73
42
  * - Constraint validation: auto-validates required, length, etc. from field meta
74
- * - Draft auto-save: creates draft on form open, patches on field changes
43
+ * - Per-field sync: validates, sends to API, resets dirty, updates computed fields
75
44
  */
76
- export function useForm<B extends BaseBdo<any, any, any>>(
77
- options: UseFormOptionsType<B>,
78
- ): UseFormReturnType<B> {
45
+ export function useBDOForm<B extends BaseBdo<any, any, any>>(
46
+ options: UseBDOFormOptionsType<B>,
47
+ ): UseBDOFormReturnType<B> {
79
48
  const {
80
49
  bdo,
81
50
  recordId,
@@ -112,7 +81,7 @@ export function useForm<B extends BaseBdo<any, any, any>>(
112
81
  queryKey: ["form-record", bdo.meta._id, recordId],
113
82
  queryFn: async () => {
114
83
  const item = await (bdo as unknown as UpdatableBdo).get(recordId!);
115
- return coerceRecordForForm(bdo, item.toJSON() as Record<string, unknown>);
84
+ return coerceRecordForForm(bdo.getFields(), item.toJSON() as Record<string, unknown>);
116
85
  },
117
86
  enabled: operation === "update" && !!recordId,
118
87
  staleTime: 0,
@@ -199,66 +168,71 @@ export function useForm<B extends BaseBdo<any, any, any>>(
199
168
  );
200
169
 
201
170
  // ============================================================
202
- // SMART REGISTER (auto-disables readonly fields)
171
+ // PER-FIELD SYNC (validate API call → reset dirty → update computed)
203
172
  // ============================================================
204
173
 
205
174
  const fields = bdo.getFields();
175
+ const isComputingRef = useRef(false);
206
176
 
207
- const smartRegister = useCallback(
208
- (name: string, registerOptions?: RegisterOptions) => {
209
- const rhfResult = form.register(name as any, registerOptions);
210
- if (fields[name]?.readOnly) {
211
- return { ...rhfResult, disabled: true };
212
- }
213
- return rhfResult;
214
- },
215
- [form, fields],
177
+ const readonlyFieldNames = useMemo<string[]>(
178
+ () => Object.keys(fields).filter((k) => fields[k].readOnly),
179
+ [fields],
216
180
  );
217
181
 
218
- // ============================================================
219
- // DRAFT AUTO-SAVE (Create Mode - patch dirty fields on change)
220
- // ============================================================
221
-
222
- const draftPatchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
223
-
224
- useEffect(() => {
225
- if (operation !== "create" || !draftData?._id) return;
226
-
227
- const subscription = form.watch((_values, { type }) => {
228
- if (type !== "change") return;
229
-
230
- if (draftPatchTimeoutRef.current) {
231
- clearTimeout(draftPatchTimeoutRef.current);
182
+ const syncApiFn = useCallback(
183
+ async (fieldName: string, value: unknown) => {
184
+ if (operation === "create" && draftData?._id) {
185
+ return api(bdo.meta._id).draftInteraction({
186
+ _id: draftData._id,
187
+ [fieldName]: value,
188
+ });
189
+ } else if (operation === "update" && recordId) {
190
+ return api(bdo.meta._id).update(recordId, {
191
+ [fieldName]: value,
192
+ });
232
193
  }
194
+ },
195
+ [operation, draftData, recordId, bdo],
196
+ );
233
197
 
234
- draftPatchTimeoutRef.current = setTimeout(async () => {
235
- const currentValues = form.getValues();
236
- const dirtyFields = form.formState.dirtyFields;
237
- const dirtyData: Record<string, unknown> = {};
198
+ const syncField = useMemo(
199
+ () =>
200
+ createSyncField({
201
+ apiFn: syncApiFn,
202
+ allFields: fields,
203
+ readonlyFieldNames,
204
+ rhf: form,
205
+ isComputingRef,
206
+ }),
207
+ [syncApiFn, fields, readonlyFieldNames, form],
208
+ );
238
209
 
239
- for (const [key, value] of Object.entries(currentValues)) {
240
- if (fields[key] && !fields[key].readOnly && dirtyFields[key]) {
241
- dirtyData[key] = coerceFieldValue(fields[key], value);
242
- }
243
- }
210
+ const syncOnChange = mode === "onChange" || mode === "all";
211
+ const syncOnBlur =
212
+ mode === "onBlur" || mode === "onTouched" || mode === "all";
213
+
214
+ const smartRegister = useMemo(
215
+ () =>
216
+ createEnhancedRegister({
217
+ rhf: form,
218
+ allFields: fields,
219
+ syncField,
220
+ syncOnBlur,
221
+ syncOnChange,
222
+ }),
223
+ [form, fields, syncField, syncOnBlur, syncOnChange],
224
+ );
244
225
 
245
- if (Object.keys(dirtyData).length > 0) {
246
- try {
247
- await api(bdo.meta._id).draftInteraction({ _id: draftData._id, ...dirtyData });
248
- } catch {
249
- // Draft auto-save is best-effort — don't block user interaction
250
- }
251
- }
252
- }, 800);
253
- });
254
-
255
- return () => {
256
- subscription.unsubscribe();
257
- if (draftPatchTimeoutRef.current) {
258
- clearTimeout(draftPatchTimeoutRef.current);
259
- }
260
- };
261
- }, [form, operation, draftData, fields, bdo]);
226
+ const enhancedControl = useMemo(
227
+ () =>
228
+ createEnhancedControl({
229
+ control: form.control,
230
+ syncField,
231
+ syncOnBlur,
232
+ syncOnChange,
233
+ }),
234
+ [form.control, syncField, syncOnBlur, syncOnChange],
235
+ );
262
236
 
263
237
  // ============================================================
264
238
  // CUSTOM HANDLE SUBMIT (with API call + payload filtering)
@@ -332,7 +306,7 @@ export function useForm<B extends BaseBdo<any, any, any>>(
332
306
  getValues: form.getValues as any,
333
307
  reset: form.reset as any,
334
308
  trigger: form.trigger as any,
335
- control: form.control as unknown as Control<AllFieldsType<B>>,
309
+ control: enhancedControl as unknown as Control<AllFieldsType<B>>,
336
310
  formState: form.formState as any,
337
311
 
338
312
  errors: form.formState.errors as any,
@@ -0,0 +1,2 @@
1
+ export { useBDOTable } from './useBDOTable';
2
+ export type { UseBDOTableOptionsType, UseBDOTableReturnType } from './types';
@@ -0,0 +1,22 @@
1
+ import type { UseTableReturnType, PaginationStateType } from '../useTable/types';
2
+ import type { UseFilterOptionsType } from '../useFilter/types';
3
+ import type { SortType } from '../../../types/common';
4
+
5
+ export interface UseBDOTableOptionsType<T> {
6
+ /** BDO instance — only meta._id is used (for API routing) */
7
+ bdo: {
8
+ meta: { readonly _id: string; readonly name: string };
9
+ };
10
+ /** Initial state */
11
+ initialState?: {
12
+ sort?: SortType;
13
+ pagination?: PaginationStateType;
14
+ filter?: UseFilterOptionsType<T>;
15
+ };
16
+ /** Error callback */
17
+ onError?: (error: Error) => void;
18
+ /** Success callback — receives rows from current page */
19
+ onSuccess?: (data: T[]) => void;
20
+ }
21
+
22
+ export type UseBDOTableReturnType<T> = UseTableReturnType<T>;
@@ -0,0 +1,16 @@
1
+ import { api } from '../../../api/client';
2
+ import { useTable } from '../useTable';
3
+ import type { UseBDOTableOptionsType, UseBDOTableReturnType } from './types';
4
+
5
+ export function useBDOTable<T = any>(
6
+ options: UseBDOTableOptionsType<T>,
7
+ ): UseBDOTableReturnType<T> {
8
+ const { bdo, ...rest } = options;
9
+
10
+ return useTable<T>({
11
+ queryKey: ['table', bdo.meta._id],
12
+ listFn: (opts) => api<T>(bdo.meta._id).list(opts),
13
+ countFn: (opts) => api<T>(bdo.meta._id).count(opts),
14
+ ...rest,
15
+ });
16
+ }
@@ -1,9 +1,9 @@
1
1
  // Main hook
2
- export { useTable } from "./useTable";
2
+ export { useTable } from './useTable';
3
3
 
4
4
  // Types
5
5
  export type {
6
- ColumnDefinitionType,
7
6
  UseTableOptionsType,
8
7
  UseTableReturnType,
9
- } from "./types";
8
+ PaginationStateType,
9
+ } from './types';
@@ -1,8 +1,10 @@
1
- import type { ListResponseType, SortType, ColumnDefinitionType } from "../../../types/common";
2
-
3
- // Re-export ColumnDefinitionType for backwards compatibility
4
- export type { ColumnDefinitionType };
5
- import type { UseFilterReturnType, UseFilterOptionsType } from "../useFilter";
1
+ import type {
2
+ ListResponseType,
3
+ ListOptionsType,
4
+ CountResponseType,
5
+ SortType,
6
+ } from '../../../types/common';
7
+ import type { UseFilterReturnType, UseFilterOptionsType } from '../useFilter';
6
8
 
7
9
  // ============================================================
8
10
  // STATE TYPE DEFINITIONS
@@ -23,10 +25,12 @@ export interface PaginationStateType {
23
25
  // ============================================================
24
26
 
25
27
  export interface UseTableOptionsType<T> {
26
- /** Data source identifier */
27
- source: string;
28
- /** Column configurations */
29
- columns: ColumnDefinitionType<T>[];
28
+ /** Unique query key for React Query caching */
29
+ queryKey: string[];
30
+ /** Fetch list data (POST with filter/sort/pagination) */
31
+ listFn: (options: ListOptionsType) => Promise<ListResponseType<T>>;
32
+ /** Fetch count (POST with filter only) */
33
+ countFn: (options: ListOptionsType) => Promise<CountResponseType>;
30
34
  /** Initial state */
31
35
  initialState?: {
32
36
  /** Sort configuration: [{ "fieldName": "ASC" }] */
@@ -38,7 +42,7 @@ export interface UseTableOptionsType<T> {
38
42
  };
39
43
  /** Error callback */
40
44
  onError?: (error: Error) => void;
41
- /** Success callback */
45
+ /** Success callback — receives rows from current page */
42
46
  onSuccess?: (data: T[]) => void;
43
47
  }
44
48
 
@@ -65,10 +69,10 @@ export interface UseTableReturnType<T> {
65
69
  // Sorting (Flat Access)
66
70
  sort: {
67
71
  field: keyof T | null;
68
- direction: "ASC" | "DESC" | null;
72
+ direction: 'ASC' | 'DESC' | null;
69
73
  toggle: (field: keyof T) => void;
70
74
  clear: () => void;
71
- set: (field: keyof T, direction: "ASC" | "DESC") => void;
75
+ set: (field: keyof T | null, direction: 'ASC' | 'DESC' | null) => void;
72
76
  };
73
77
 
74
78
  // Filter (Simplified chainable API)