@ram_28/kf-ai-sdk 2.0.3 → 2.0.5

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 (67) hide show
  1. package/dist/api.mjs +1 -1
  2. package/dist/auth.mjs +1 -1
  3. package/dist/bdo/core/Item.d.ts.map +1 -1
  4. package/dist/bdo/core/types.d.ts +1 -0
  5. package/dist/bdo/core/types.d.ts.map +1 -1
  6. package/dist/bdo.cjs +1 -1
  7. package/dist/bdo.mjs +91 -86
  8. package/dist/components/hooks/useForm/createItemProxy.d.ts.map +1 -1
  9. package/dist/components/hooks/useForm/index.d.ts +1 -1
  10. package/dist/components/hooks/useForm/index.d.ts.map +1 -1
  11. package/dist/components/hooks/useForm/types.d.ts +6 -16
  12. package/dist/components/hooks/useForm/types.d.ts.map +1 -1
  13. package/dist/components/hooks/useForm/useForm.d.ts +0 -1
  14. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
  15. package/dist/components/hooks/useTable/types.d.ts +2 -2
  16. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
  17. package/dist/{constants-BQrBcCON.js → constants-CYJih7y4.js} +2 -2
  18. package/dist/filter.mjs +1 -1
  19. package/dist/form.cjs +1 -1
  20. package/dist/form.mjs +158 -278
  21. package/dist/form.types.d.ts +1 -1
  22. package/dist/form.types.d.ts.map +1 -1
  23. package/dist/table.cjs +1 -1
  24. package/dist/table.mjs +65 -65
  25. package/dist/types/constants.d.ts +11 -11
  26. package/dist/types/constants.d.ts.map +1 -1
  27. package/dist/utils/api/buildListOptions.d.ts +1 -1
  28. package/dist/utils/api/buildListOptions.d.ts.map +1 -1
  29. package/dist/workflow/Activity.d.ts +14 -5
  30. package/dist/workflow/Activity.d.ts.map +1 -1
  31. package/dist/workflow/ActivityInstance.d.ts +1 -1
  32. package/dist/workflow/ActivityInstance.d.ts.map +1 -1
  33. package/dist/workflow/client.d.ts +14 -4
  34. package/dist/workflow/client.d.ts.map +1 -1
  35. package/dist/workflow/components/useActivityForm/createActivityItemProxy.d.ts.map +1 -1
  36. package/dist/workflow/types.d.ts +26 -9
  37. package/dist/workflow/types.d.ts.map +1 -1
  38. package/dist/workflow.cjs +1 -1
  39. package/dist/workflow.mjs +296 -240
  40. package/docs/bdo.md +63 -0
  41. package/docs/useAuth.md +27 -0
  42. package/docs/useFilter.md +67 -0
  43. package/docs/useForm.md +59 -0
  44. package/docs/useTable.md +106 -13
  45. package/docs/workflow.md +93 -49
  46. package/package.json +2 -2
  47. package/sdk/api/metadata.ts +4 -4
  48. package/sdk/bdo/core/Item.ts +8 -0
  49. package/sdk/bdo/core/types.ts +1 -0
  50. package/sdk/components/hooks/useForm/createItemProxy.ts +8 -0
  51. package/sdk/components/hooks/useForm/index.ts +0 -1
  52. package/sdk/components/hooks/useForm/types.ts +7 -18
  53. package/sdk/components/hooks/useForm/useForm.ts +30 -112
  54. package/sdk/components/hooks/useTable/types.ts +2 -2
  55. package/sdk/components/hooks/useTable/useTable.llm.txt +7 -7
  56. package/sdk/components/hooks/useTable/useTable.ts +9 -8
  57. package/sdk/form.types.ts +0 -1
  58. package/sdk/types/constants.ts +11 -11
  59. package/sdk/utils/api/buildListOptions.ts +2 -3
  60. package/sdk/workflow/Activity.ts +31 -10
  61. package/sdk/workflow/ActivityInstance.ts +1 -1
  62. package/sdk/workflow/client.ts +73 -10
  63. package/sdk/workflow/components/useActivityForm/createActivityItemProxy.ts +4 -0
  64. package/sdk/workflow/types.ts +22 -9
  65. package/dist/components/hooks/useForm/useDraftInteraction.d.ts +0 -26
  66. package/dist/components/hooks/useForm/useDraftInteraction.d.ts.map +0 -1
  67. package/sdk/components/hooks/useForm/useDraftInteraction.ts +0 -251
@@ -10,9 +10,11 @@
10
10
  // - Typed field instances as class properties
11
11
  //
12
12
  // Methods:
13
- // activity.getInstanceList(options) // list activity instances
14
- // activity.instanceMetrics(options) // get aggregated metrics
15
- // activity.getInstance(instanceId) // get typed ActivityInstance
13
+ // activity.getInProgressList(options) // list in-progress activity instances
14
+ // activity.getCompletedList(options) // list completed activity instances
15
+ // activity.inProgressMetrics(options) // get in-progress aggregated metrics
16
+ // activity.completedMetrics(options) // get completed aggregated metrics
17
+ // activity.getInstance(instanceId) // get typed ActivityInstance
16
18
 
17
19
  import { Workflow } from "./client";
18
20
  import { createActivityInstance } from "./ActivityInstance";
@@ -58,7 +60,8 @@ import { BaseField } from "../bdo/fields/BaseField";
58
60
  * }
59
61
  *
60
62
  * const activity = new EmployeeInputActivity();
61
- * const items = await activity.getInstanceList({ Page: 1, PageSize: 10 });
63
+ * const inProgress = await activity.getInProgressList({ Page: 1, PageSize: 10 });
64
+ * const completed = await activity.getCompletedList({ Page: 1, PageSize: 10 });
62
65
  * const instance = await activity.getInstance("inst_123");
63
66
  * instance.StartDate.get(); // typed value
64
67
  * instance.StartDate.set("2026-03-01");
@@ -122,21 +125,39 @@ export abstract class Activity<
122
125
  // ============================================================
123
126
 
124
127
  /**
125
- * List activity instances with optional filtering/pagination.
128
+ * List in-progress activity instances with optional filtering/pagination.
126
129
  */
127
- async getInstanceList(
130
+ async getInProgressList(
128
131
  options?: ListOptionsType,
129
132
  ): Promise<ListResponseType<ActivityInstanceFieldsType & TEntity>> {
130
- return this._ops().list(options);
133
+ return this._ops().inProgressList(options);
131
134
  }
132
135
 
133
136
  /**
134
- * Get aggregated metrics for activity instances.
137
+ * List completed activity instances with optional filtering/pagination.
135
138
  */
136
- async instanceMetrics(
139
+ async getCompletedList(
140
+ options?: ListOptionsType,
141
+ ): Promise<ListResponseType<ActivityInstanceFieldsType & TEntity>> {
142
+ return this._ops().completedList(options);
143
+ }
144
+
145
+ /**
146
+ * Get aggregated metrics for in-progress activity instances.
147
+ */
148
+ async inProgressMetrics(
149
+ options: Omit<MetricOptionsType, "Type">,
150
+ ): Promise<MetricResponseType> {
151
+ return this._ops().inProgressMetric(options);
152
+ }
153
+
154
+ /**
155
+ * Get aggregated metrics for completed activity instances.
156
+ */
157
+ async completedMetrics(
137
158
  options: Omit<MetricOptionsType, "Type">,
138
159
  ): Promise<MetricResponseType> {
139
- return this._ops().metric(options);
160
+ return this._ops().completedMetric(options);
140
161
  }
141
162
 
142
163
  /**
@@ -172,7 +172,7 @@ export class ActivityInstance<
172
172
  /**
173
173
  * Get activity progress — calls ActivityOperations.progress()
174
174
  */
175
- async progress(): Promise<ActivityProgressType> {
175
+ async progress(): Promise<ActivityProgressType[]> {
176
176
  return this._ops.progress(this._id);
177
177
  }
178
178
 
@@ -41,9 +41,14 @@ import type {
41
41
  * await act.draftEnd("inst_123", data);
42
42
  * await act.complete("inst_123");
43
43
  *
44
- * // List operations
45
- * await act.list({ Page: 1, PageSize: 10 });
46
- * await act.metric({ Metric: [...] });
44
+ * // List operations (by status)
45
+ * await act.inProgressList({ Page: 1, PageSize: 10 });
46
+ * await act.completedList({ Page: 1, PageSize: 10 });
47
+ * await act.inProgressMetric({ Metric: [...] });
48
+ * await act.completedMetric({ Metric: [...] });
49
+ *
50
+ * // Global process progress
51
+ * const progress = await wf.progress();
47
52
  * ```
48
53
  */
49
54
  export class Workflow<T = any> {
@@ -72,6 +77,26 @@ export class Workflow<T = any> {
72
77
  return response.json();
73
78
  }
74
79
 
80
+ /**
81
+ * Get global progress across the entire business process.
82
+ * Returns a list of progress entries for each stage/activity.
83
+ */
84
+ async progress(): Promise<ActivityProgressType[]> {
85
+ const response = await fetch(
86
+ `${getApiBaseUrl()}/api/app/process/${this.bp_id}/progress`,
87
+ {
88
+ method: "GET",
89
+ headers: getDefaultHeaders(),
90
+ }
91
+ );
92
+
93
+ if (!response.ok) {
94
+ throw new Error(`Failed to get process progress: ${response.statusText}`);
95
+ }
96
+
97
+ return response.json();
98
+ }
99
+
75
100
  /**
76
101
  * Get all operations for a specific activity
77
102
  * @param activity_id - Activity identifier
@@ -82,39 +107,77 @@ export class Workflow<T = any> {
82
107
  return {
83
108
  // ── List-level ────────────────────────────────────────────
84
109
 
85
- async list(options?: ListOptionsType): Promise<ListResponseType<ActivityInstanceFieldsType & T>> {
110
+ async inProgressList(options?: ListOptionsType): Promise<ListResponseType<ActivityInstanceFieldsType & T>> {
86
111
  const requestBody: ListOptionsType = {
87
112
  Type: "List",
88
113
  ...options,
89
114
  };
90
115
 
91
- const response = await fetch(`${getApiBaseUrl()}${base}/list`, {
116
+ const response = await fetch(`${getApiBaseUrl()}${base}/inprogress/list`, {
117
+ method: "POST",
118
+ headers: getDefaultHeaders(),
119
+ body: JSON.stringify(requestBody),
120
+ });
121
+
122
+ if (!response.ok) {
123
+ throw new Error(`Failed to list in-progress activities: ${response.statusText}`);
124
+ }
125
+
126
+ return response.json();
127
+ },
128
+
129
+ async completedList(options?: ListOptionsType): Promise<ListResponseType<ActivityInstanceFieldsType & T>> {
130
+ const requestBody: ListOptionsType = {
131
+ Type: "List",
132
+ ...options,
133
+ };
134
+
135
+ const response = await fetch(`${getApiBaseUrl()}${base}/completed/list`, {
136
+ method: "POST",
137
+ headers: getDefaultHeaders(),
138
+ body: JSON.stringify(requestBody),
139
+ });
140
+
141
+ if (!response.ok) {
142
+ throw new Error(`Failed to list completed activities: ${response.statusText}`);
143
+ }
144
+
145
+ return response.json();
146
+ },
147
+
148
+ async inProgressMetric(options: Omit<MetricOptionsType, "Type">): Promise<MetricResponseType> {
149
+ const requestBody: MetricOptionsType = {
150
+ Type: "Metric",
151
+ ...options,
152
+ };
153
+
154
+ const response = await fetch(`${getApiBaseUrl()}${base}/inprogress/metric`, {
92
155
  method: "POST",
93
156
  headers: getDefaultHeaders(),
94
157
  body: JSON.stringify(requestBody),
95
158
  });
96
159
 
97
160
  if (!response.ok) {
98
- throw new Error(`Failed to list activities: ${response.statusText}`);
161
+ throw new Error(`Failed to get in-progress activity metrics: ${response.statusText}`);
99
162
  }
100
163
 
101
164
  return response.json();
102
165
  },
103
166
 
104
- async metric(options: Omit<MetricOptionsType, "Type">): Promise<MetricResponseType> {
167
+ async completedMetric(options: Omit<MetricOptionsType, "Type">): Promise<MetricResponseType> {
105
168
  const requestBody: MetricOptionsType = {
106
169
  Type: "Metric",
107
170
  ...options,
108
171
  };
109
172
 
110
- const response = await fetch(`${getApiBaseUrl()}${base}/metric`, {
173
+ const response = await fetch(`${getApiBaseUrl()}${base}/completed/metric`, {
111
174
  method: "POST",
112
175
  headers: getDefaultHeaders(),
113
176
  body: JSON.stringify(requestBody),
114
177
  });
115
178
 
116
179
  if (!response.ok) {
117
- throw new Error(`Failed to get activity metrics: ${response.statusText}`);
180
+ throw new Error(`Failed to get completed activity metrics: ${response.statusText}`);
118
181
  }
119
182
 
120
183
  return response.json();
@@ -191,7 +254,7 @@ export class Workflow<T = any> {
191
254
  return response.json();
192
255
  },
193
256
 
194
- async progress(instanceId: string): Promise<ActivityProgressType> {
257
+ async progress(instanceId: string): Promise<ActivityProgressType[]> {
195
258
  const response = await fetch(`${getApiBaseUrl()}${base}/${instanceId}/progress`, {
196
259
  method: "GET",
197
260
  headers: getDefaultHeaders(),
@@ -80,6 +80,8 @@ export function createActivityItemProxy<A extends Activity<any, any, any>>(
80
80
  defaultValue: bdoField?.defaultValue,
81
81
  meta: fieldMeta,
82
82
  get: () => form.getValues(prop as Path<FieldValues>),
83
+ getOrDefault: (fallback: unknown) =>
84
+ form.getValues(prop as Path<FieldValues>) ?? fallback,
83
85
  set: (value: unknown) => {
84
86
  form.setValue(prop as Path<FieldValues>, value as any, {
85
87
  shouldDirty: true,
@@ -99,6 +101,8 @@ export function createActivityItemProxy<A extends Activity<any, any, any>>(
99
101
  defaultValue: bdoField?.defaultValue,
100
102
  meta: fieldMeta,
101
103
  get: () => form.getValues(prop as Path<FieldValues>),
104
+ getOrDefault: (fallback: unknown) =>
105
+ form.getValues(prop as Path<FieldValues>) ?? fallback,
102
106
  validate,
103
107
  };
104
108
  return accessor;
@@ -28,12 +28,19 @@ export interface WorkflowStartResponseType {
28
28
  }
29
29
 
30
30
  /**
31
- * Response from the activity progress endpoint
31
+ * Response from the activity progress endpoint.
32
+ * Both global (Workflow.progress()) and instance-level (ActivityInstance.progress())
33
+ * return ActivityProgressType[].
32
34
  */
33
35
  export interface ActivityProgressType {
34
- Stage?: string;
35
- Progress?: number;
36
- [key: string]: any;
36
+ ActivityId: string;
37
+ ActivityInstanceId: string;
38
+ ActivityType: string;
39
+ AssignedTo: { Type: string; _id: string }[];
40
+ CompletedAt: string | null;
41
+ CompletedBy: { _id: string; _name: string } | null;
42
+ Status: "COMPLETED" | "IN_PROGRESS";
43
+ _name: string;
37
44
  }
38
45
 
39
46
  /**
@@ -56,11 +63,17 @@ export type ActivityInstanceFieldsType = {
56
63
  export interface ActivityOperations<T> {
57
64
  // ── List-level ──────────────────────────────────────────────
58
65
 
59
- /** List activity instances (POST .../list) */
60
- list(options?: ListOptionsType): Promise<ListResponseType<ActivityInstanceFieldsType & T>>;
66
+ /** List in-progress activity instances (POST .../inprogress/list) */
67
+ inProgressList(options?: ListOptionsType): Promise<ListResponseType<ActivityInstanceFieldsType & T>>;
61
68
 
62
- /** Get activity metrics (POST .../metric) */
63
- metric(options: Omit<MetricOptionsType, "Type">): Promise<MetricResponseType>;
69
+ /** List completed activity instances (POST .../completed/list) */
70
+ completedList(options?: ListOptionsType): Promise<ListResponseType<ActivityInstanceFieldsType & T>>;
71
+
72
+ /** Get in-progress activity metrics (POST .../inprogress/metric) */
73
+ inProgressMetric(options: Omit<MetricOptionsType, "Type">): Promise<MetricResponseType>;
74
+
75
+ /** Get completed activity metrics (POST .../completed/metric) */
76
+ completedMetric(options: Omit<MetricOptionsType, "Type">): Promise<MetricResponseType>;
64
77
 
65
78
  // ── Instance-level ──────────────────────────────────────────
66
79
 
@@ -80,5 +93,5 @@ export interface ActivityOperations<T> {
80
93
  complete(instanceId: string): Promise<CreateUpdateResponseType>;
81
94
 
82
95
  /** Get activity progress (GET .../progress) */
83
- progress(instanceId: string): Promise<ActivityProgressType>;
96
+ progress(instanceId: string): Promise<ActivityProgressType[]>;
84
97
  }
@@ -1,26 +0,0 @@
1
- import type { UseFormReturn, FieldValues } from "react-hook-form";
2
- import type { BaseField } from "../../../bdo/fields/BaseField";
3
- import type { InteractiveCreatableBdo } from "./types";
4
- interface UseDraftInteractionOptions {
5
- /** The BDO instance — must expose interactive draft methods */
6
- bdo: InteractiveCreatableBdo;
7
- /** RHF form instance */
8
- form: UseFormReturn<FieldValues>;
9
- /** RHF validation mode */
10
- mode: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all";
11
- /** BDO field definitions (for determining dirty fields) */
12
- fields: Record<string, BaseField<unknown>>;
13
- /** Whether interactive mode is enabled */
14
- enabled: boolean;
15
- }
16
- interface UseDraftInteractionReturn {
17
- draftId: string | undefined;
18
- isInitializingDraft: boolean;
19
- isInteracting: boolean;
20
- interactionError: Error | null;
21
- triggerInteraction: () => void;
22
- commitDraft: (dirtyData: Record<string, unknown>) => Promise<unknown>;
23
- }
24
- export declare function useDraftInteraction(options: UseDraftInteractionOptions): UseDraftInteractionReturn;
25
- export {};
26
- //# sourceMappingURL=useDraftInteraction.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useDraftInteraction.d.ts","sourceRoot":"","sources":["../../../../sdk/components/hooks/useForm/useDraftInteraction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAMvD,UAAU,0BAA0B;IAClC,+DAA+D;IAC/D,GAAG,EAAE,uBAAuB,CAAC;IAC7B,wBAAwB;IACxB,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACjC,0BAA0B;IAC1B,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,CAAC;IAC/D,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3C,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,KAAK,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE;AAYD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,yBAAyB,CA+M3B"}
@@ -1,251 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from "react";
2
- import type { UseFormReturn, FieldValues } from "react-hook-form";
3
- import type { BaseField } from "../../../bdo/fields/BaseField";
4
- import type { InteractiveCreatableBdo } from "./types";
5
-
6
- // ============================================================
7
- // TYPES
8
- // ============================================================
9
-
10
- interface UseDraftInteractionOptions {
11
- /** The BDO instance — must expose interactive draft methods */
12
- bdo: InteractiveCreatableBdo;
13
- /** RHF form instance */
14
- form: UseFormReturn<FieldValues>;
15
- /** RHF validation mode */
16
- mode: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all";
17
- /** BDO field definitions (for determining dirty fields) */
18
- fields: Record<string, BaseField<unknown>>;
19
- /** Whether interactive mode is enabled */
20
- enabled: boolean;
21
- }
22
-
23
- interface UseDraftInteractionReturn {
24
- draftId: string | undefined;
25
- isInitializingDraft: boolean;
26
- isInteracting: boolean;
27
- interactionError: Error | null;
28
- triggerInteraction: () => void;
29
- commitDraft: (dirtyData: Record<string, unknown>) => Promise<unknown>;
30
- }
31
-
32
- // ============================================================
33
- // DEBOUNCE DELAY
34
- // ============================================================
35
-
36
- const DEBOUNCE_MS = 300;
37
-
38
- // ============================================================
39
- // HOOK
40
- // ============================================================
41
-
42
- export function useDraftInteraction(
43
- options: UseDraftInteractionOptions,
44
- ): UseDraftInteractionReturn {
45
- const { bdo, form, mode, fields, enabled } = options;
46
-
47
- // ============================================================
48
- // STATE
49
- // ============================================================
50
-
51
- const [draftId, setDraftId] = useState<string | undefined>(undefined);
52
- const [isInitializingDraft, setIsInitializingDraft] = useState(false);
53
- const [isInteracting, setIsInteracting] = useState(false);
54
- const [interactionError, setInteractionError] = useState<Error | null>(null);
55
-
56
- // ============================================================
57
- // REFS (for concurrency control & loop prevention)
58
- // ============================================================
59
-
60
- /** Tracks the latest interaction call — stale responses are discarded */
61
- const interactionCounterRef = useRef(0);
62
-
63
- /** Prevents re-trigger when applying computed values via setValue */
64
- const isApplyingComputedRef = useRef(false);
65
-
66
- /** Debounce timer for onChange mode */
67
- const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
68
-
69
- /** AbortController for cleanup on unmount */
70
- const abortControllerRef = useRef<AbortController | null>(null);
71
-
72
- // ============================================================
73
- // DRAFT INITIALIZATION (Create mode only)
74
- // ============================================================
75
-
76
- useEffect(() => {
77
- if (!enabled) return;
78
-
79
- let cancelled = false;
80
- setIsInitializingDraft(true);
81
-
82
- const controller = new AbortController();
83
- abortControllerRef.current = controller;
84
-
85
- bdo
86
- .draftInteraction({})
87
- .then((response) => {
88
- if (cancelled || controller.signal.aborted) return;
89
- const id = response._id;
90
- setDraftId(id);
91
- // Set _id into form without marking dirty
92
- form.setValue("_id" as any, id, { shouldDirty: false });
93
- setInteractionError(null);
94
- })
95
- .catch((error) => {
96
- if (cancelled || controller.signal.aborted) return;
97
- setInteractionError(error instanceof Error ? error : new Error(String(error)));
98
- })
99
- .finally(() => {
100
- if (!cancelled) setIsInitializingDraft(false);
101
- });
102
-
103
- return () => {
104
- cancelled = true;
105
- controller.abort();
106
- };
107
- }, [enabled, bdo, form]);
108
-
109
- // ============================================================
110
- // CORE INTERACTION LOGIC
111
- // ============================================================
112
-
113
- const executeInteraction = useCallback(async () => {
114
- if (!enabled || !draftId) return;
115
- if (isApplyingComputedRef.current) return;
116
-
117
- // Build payload from dirty fields
118
- const dirtyFields = form.formState.dirtyFields;
119
- const allValues = form.getValues();
120
- const payload: Record<string, unknown> = {};
121
-
122
- for (const [key, isDirty] of Object.entries(dirtyFields)) {
123
- if (isDirty && fields[key] && !fields[key].readOnly) {
124
- payload[key] = allValues[key];
125
- }
126
- }
127
-
128
- // Skip if nothing changed
129
- if (Object.keys(payload).length === 0) return;
130
-
131
- // Increment counter for concurrency control
132
- const currentCounter = ++interactionCounterRef.current;
133
-
134
- setIsInteracting(true);
135
-
136
- try {
137
- const response = await bdo.draftInteraction({
138
- _id: draftId,
139
- ...payload,
140
- } as any);
141
-
142
- // Only apply if this is still the latest interaction
143
- if (currentCounter !== interactionCounterRef.current) return;
144
-
145
- // Apply computed values back to form
146
- isApplyingComputedRef.current = true;
147
- try {
148
- for (const [key, value] of Object.entries(response)) {
149
- // Skip _id and fields the user has dirty (don't overwrite user input)
150
- if (key === "_id") continue;
151
- if (dirtyFields[key]) continue;
152
-
153
- form.setValue(key as any, value, {
154
- shouldDirty: false,
155
- shouldValidate: false,
156
- });
157
- }
158
- } finally {
159
- // Reset flag after React settles
160
- // Using setTimeout(0) ensures setValue batch is complete
161
- setTimeout(() => {
162
- isApplyingComputedRef.current = false;
163
- }, 0);
164
- }
165
-
166
- setInteractionError(null);
167
- } catch (error) {
168
- // Only set error if this is still the latest interaction
169
- if (currentCounter !== interactionCounterRef.current) return;
170
- setInteractionError(error instanceof Error ? error : new Error(String(error)));
171
- } finally {
172
- if (currentCounter === interactionCounterRef.current) {
173
- setIsInteracting(false);
174
- }
175
- }
176
- }, [enabled, draftId, form, fields, bdo]);
177
-
178
- // ============================================================
179
- // TRIGGER INTERACTION (with optional debounce)
180
- // ============================================================
181
-
182
- const triggerInteraction = useCallback(() => {
183
- if (!enabled) return;
184
-
185
- // For onChange/all modes, debounce the interaction
186
- if (mode === "onChange" || mode === "all") {
187
- if (debounceTimerRef.current) {
188
- clearTimeout(debounceTimerRef.current);
189
- }
190
- debounceTimerRef.current = setTimeout(() => {
191
- executeInteraction();
192
- }, DEBOUNCE_MS);
193
- } else {
194
- // For onBlur/onTouched/onSubmit, trigger immediately
195
- executeInteraction();
196
- }
197
- }, [enabled, mode, executeInteraction]);
198
-
199
- // ============================================================
200
- // COMMIT DRAFT (for handleSubmit)
201
- // ============================================================
202
-
203
- const commitDraft = useCallback(
204
- async (dirtyData: Record<string, unknown>): Promise<unknown> => {
205
- return bdo.draft({
206
- _id: draftId,
207
- ...dirtyData,
208
- } as any);
209
- },
210
- [bdo, draftId],
211
- );
212
-
213
- // ============================================================
214
- // CLEANUP
215
- // ============================================================
216
-
217
- useEffect(() => {
218
- return () => {
219
- if (debounceTimerRef.current) {
220
- clearTimeout(debounceTimerRef.current);
221
- }
222
- abortControllerRef.current?.abort();
223
- };
224
- }, []);
225
-
226
- // ============================================================
227
- // DISABLED MODE (return no-ops)
228
- // ============================================================
229
-
230
- if (!enabled) {
231
- return {
232
- draftId: undefined,
233
- isInitializingDraft: false,
234
- isInteracting: false,
235
- interactionError: null,
236
- triggerInteraction: () => {},
237
- commitDraft: async () => {
238
- throw new Error("Draft interaction is not enabled");
239
- },
240
- };
241
- }
242
-
243
- return {
244
- draftId,
245
- isInitializingDraft,
246
- isInteracting,
247
- interactionError,
248
- triggerInteraction,
249
- commitDraft,
250
- };
251
- }