@ram_28/kf-ai-sdk 1.0.10 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +10 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/components/hooks/useForm/types.d.ts +18 -0
- package/dist/components/hooks/useForm/types.d.ts.map +1 -1
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/index.cjs +12 -12
- package/dist/index.mjs +2405 -2345
- package/package.json +1 -1
- package/sdk/api/client.ts +27 -0
- package/sdk/components/hooks/useForm/types.ts +26 -0
- package/sdk/components/hooks/useForm/useForm.ts +148 -22
package/package.json
CHANGED
package/sdk/api/client.ts
CHANGED
|
@@ -69,6 +69,15 @@ export interface ResourceClient<T = any> {
|
|
|
69
69
|
*/
|
|
70
70
|
draftPatch(id: string, data: Partial<T>): Promise<DraftResponse>;
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Interactive draft - create/update draft without instance ID
|
|
74
|
+
* PATCH /{bo_id}/draft
|
|
75
|
+
* Used in interactive mode for create operations
|
|
76
|
+
*/
|
|
77
|
+
draftInteraction(
|
|
78
|
+
data: Partial<T> & { _id?: string }
|
|
79
|
+
): Promise<DraftResponse & { _id: string }>;
|
|
80
|
+
|
|
72
81
|
// ============================================================
|
|
73
82
|
// QUERY OPERATIONS
|
|
74
83
|
// ============================================================
|
|
@@ -355,6 +364,24 @@ export function api<T = any>(bo_id: string): ResourceClient<T> {
|
|
|
355
364
|
return response.json();
|
|
356
365
|
},
|
|
357
366
|
|
|
367
|
+
async draftInteraction(
|
|
368
|
+
data: Partial<T> & { _id?: string }
|
|
369
|
+
): Promise<DraftResponse & { _id: string }> {
|
|
370
|
+
const response = await fetch(`${baseUrl}/api/app/${bo_id}/draft`, {
|
|
371
|
+
method: "PATCH",
|
|
372
|
+
headers: defaultHeaders,
|
|
373
|
+
body: JSON.stringify(data),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
if (!response.ok) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`Failed to create interactive draft for ${bo_id}: ${response.statusText}`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return response.json();
|
|
383
|
+
},
|
|
384
|
+
|
|
358
385
|
// ============================================================
|
|
359
386
|
// QUERY OPERATIONS
|
|
360
387
|
// ============================================================
|
|
@@ -229,6 +229,13 @@ export interface BDOSchema {
|
|
|
229
229
|
*/
|
|
230
230
|
export type FormOperation = "create" | "update";
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Form interaction mode
|
|
234
|
+
* - "interactive" (default): Real-time server-side validation and computation on every field blur
|
|
235
|
+
* - "non-interactive": Draft only for computed field dependencies (legacy behavior)
|
|
236
|
+
*/
|
|
237
|
+
export type InteractionMode = "interactive" | "non-interactive";
|
|
238
|
+
|
|
232
239
|
/**
|
|
233
240
|
* Form validation mode (from react-hook-form)
|
|
234
241
|
*/
|
|
@@ -295,8 +302,17 @@ export interface UseFormOptions<
|
|
|
295
302
|
* Trigger draft API call on every field change (after validation passes)
|
|
296
303
|
* If true: draft API is called for any field change
|
|
297
304
|
* If false (default): draft API is called only when computed field dependencies change
|
|
305
|
+
* @deprecated Use interactionMode: "interactive" instead
|
|
298
306
|
*/
|
|
299
307
|
draftOnEveryChange?: boolean;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Form interaction mode
|
|
311
|
+
* - "interactive" (default): Real-time server-side validation and computation on every field blur
|
|
312
|
+
* - "non-interactive": Draft only for computed field dependencies (legacy behavior)
|
|
313
|
+
* @default "interactive"
|
|
314
|
+
*/
|
|
315
|
+
interactionMode?: InteractionMode;
|
|
300
316
|
}
|
|
301
317
|
|
|
302
318
|
// ============================================================
|
|
@@ -536,6 +552,16 @@ export interface UseFormReturn<
|
|
|
536
552
|
/** Any loading state active */
|
|
537
553
|
isLoading: boolean;
|
|
538
554
|
|
|
555
|
+
// ============================================================
|
|
556
|
+
// INTERACTIVE MODE STATE
|
|
557
|
+
// ============================================================
|
|
558
|
+
|
|
559
|
+
/** Draft ID for interactive create mode */
|
|
560
|
+
draftId: string | null;
|
|
561
|
+
|
|
562
|
+
/** Whether draft is being created (interactive create only) */
|
|
563
|
+
isCreatingDraft: boolean;
|
|
564
|
+
|
|
539
565
|
// ============================================================
|
|
540
566
|
// ERROR HANDLING
|
|
541
567
|
// ============================================================
|
|
@@ -53,8 +53,12 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
53
53
|
skipSchemaFetch = false,
|
|
54
54
|
schema: manualSchema,
|
|
55
55
|
draftOnEveryChange = false,
|
|
56
|
+
interactionMode = "interactive",
|
|
56
57
|
} = options;
|
|
57
58
|
|
|
59
|
+
// Derived interaction mode flags
|
|
60
|
+
const isInteractiveMode = interactionMode === "interactive";
|
|
61
|
+
|
|
58
62
|
// ============================================================
|
|
59
63
|
// STATE MANAGEMENT
|
|
60
64
|
// ============================================================
|
|
@@ -65,6 +69,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
65
69
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
66
70
|
const [lastFormValues] = useState<Partial<T>>({});
|
|
67
71
|
|
|
72
|
+
// Interactive mode state
|
|
73
|
+
const [draftId, setDraftId] = useState<string | null>(null);
|
|
74
|
+
const [isCreatingDraft, setIsCreatingDraft] = useState(false);
|
|
75
|
+
const [draftError, setDraftError] = useState<Error | null>(null);
|
|
76
|
+
|
|
68
77
|
// Prevent infinite loop in API calls
|
|
69
78
|
const isComputingRef = useRef(false);
|
|
70
79
|
const computeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
@@ -189,6 +198,61 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
189
198
|
}
|
|
190
199
|
}, [schemaError]);
|
|
191
200
|
|
|
201
|
+
// ============================================================
|
|
202
|
+
// INTERACTIVE MODE - INITIAL DRAFT CREATION
|
|
203
|
+
// ============================================================
|
|
204
|
+
|
|
205
|
+
// Create initial draft for interactive create mode
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
// Only run for interactive mode + create operation + schema loaded + no draft yet
|
|
208
|
+
if (
|
|
209
|
+
!isInteractiveMode ||
|
|
210
|
+
operation !== "create" ||
|
|
211
|
+
!schemaConfig ||
|
|
212
|
+
!enabled ||
|
|
213
|
+
draftId
|
|
214
|
+
) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const createInitialDraft = async () => {
|
|
219
|
+
setIsCreatingDraft(true);
|
|
220
|
+
setDraftError(null);
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const client = api<T>(source);
|
|
224
|
+
// Call PATCH /{bdo_id}/draft with empty payload to get draft ID
|
|
225
|
+
const response = await client.draftInteraction({});
|
|
226
|
+
|
|
227
|
+
// Store the draft ID
|
|
228
|
+
setDraftId(response._id);
|
|
229
|
+
|
|
230
|
+
// Apply any computed fields returned from API
|
|
231
|
+
if (response && typeof response === "object") {
|
|
232
|
+
Object.entries(response).forEach(([fieldName, value]) => {
|
|
233
|
+
// Skip _id as it's the draft ID, not a form field
|
|
234
|
+
if (fieldName === "_id") return;
|
|
235
|
+
|
|
236
|
+
const currentValue = rhfForm.getValues(fieldName as any);
|
|
237
|
+
if (currentValue !== value) {
|
|
238
|
+
rhfForm.setValue(fieldName as any, value as any, {
|
|
239
|
+
shouldDirty: false,
|
|
240
|
+
shouldValidate: false,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error("Failed to create initial draft:", error);
|
|
247
|
+
setDraftError(error as Error);
|
|
248
|
+
} finally {
|
|
249
|
+
setIsCreatingDraft(false);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
createInitialDraft();
|
|
254
|
+
}, [isInteractiveMode, operation, schemaConfig, enabled, draftId, source, rhfForm]);
|
|
255
|
+
|
|
192
256
|
// ============================================================
|
|
193
257
|
// COMPUTED FIELD DEPENDENCY TRACKING AND OPTIMIZATION
|
|
194
258
|
// ============================================================
|
|
@@ -253,21 +317,29 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
253
317
|
// Manual computation trigger - called on blur after validation passes
|
|
254
318
|
const triggerComputationAfterValidation = useCallback(
|
|
255
319
|
async (fieldName: string) => {
|
|
256
|
-
if (!schemaConfig
|
|
320
|
+
if (!schemaConfig) {
|
|
257
321
|
return;
|
|
258
322
|
}
|
|
259
323
|
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
const shouldTrigger =
|
|
264
|
-
|
|
265
|
-
computedFieldDependencies.
|
|
324
|
+
// Determine if draft should be triggered based on interaction mode
|
|
325
|
+
// Interactive mode: Always trigger draft API on blur
|
|
326
|
+
// Non-interactive mode: Only trigger for computed field dependencies (legacy behavior)
|
|
327
|
+
const shouldTrigger = isInteractiveMode
|
|
328
|
+
? true // Interactive mode: always trigger
|
|
329
|
+
: (computedFieldDependencies.length > 0 &&
|
|
330
|
+
(draftOnEveryChange ||
|
|
331
|
+
computedFieldDependencies.includes(fieldName as Path<T>)));
|
|
266
332
|
|
|
267
333
|
if (!shouldTrigger) {
|
|
268
334
|
return;
|
|
269
335
|
}
|
|
270
336
|
|
|
337
|
+
// For interactive create, check that we have a draftId (except for initial draft creation)
|
|
338
|
+
if (isInteractiveMode && operation === "create" && !draftId) {
|
|
339
|
+
console.warn("Interactive create mode: waiting for draft ID");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
271
343
|
// Prevent concurrent API calls
|
|
272
344
|
if (isComputingRef.current) {
|
|
273
345
|
return;
|
|
@@ -305,6 +377,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
305
377
|
(changedFields as any)._id = (currentValues as any)._id;
|
|
306
378
|
}
|
|
307
379
|
|
|
380
|
+
// For interactive create mode, include draft _id
|
|
381
|
+
if (isInteractiveMode && operation === "create" && draftId) {
|
|
382
|
+
(changedFields as any)._id = draftId;
|
|
383
|
+
}
|
|
384
|
+
|
|
308
385
|
// Use lastSyncedValues if available, otherwise use recordData (for update) or empty object (for create)
|
|
309
386
|
const baseline =
|
|
310
387
|
lastSyncedValuesRef.current ??
|
|
@@ -357,10 +434,18 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
357
434
|
|
|
358
435
|
lastSyncedValuesRef.current = baselineBeforeApiCall;
|
|
359
436
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
437
|
+
// Choose API method based on operation and interaction mode
|
|
438
|
+
let computedFieldsResponse;
|
|
439
|
+
if (operation === "update" && recordId) {
|
|
440
|
+
// Update mode: use draftPatch (both interactive and non-interactive)
|
|
441
|
+
computedFieldsResponse = await client.draftPatch(recordId, payload);
|
|
442
|
+
} else if (isInteractiveMode && draftId) {
|
|
443
|
+
// Interactive create: use draftInteraction with _id
|
|
444
|
+
computedFieldsResponse = await client.draftInteraction(payload);
|
|
445
|
+
} else {
|
|
446
|
+
// Non-interactive create: use draft (POST)
|
|
447
|
+
computedFieldsResponse = await client.draft(payload);
|
|
448
|
+
}
|
|
364
449
|
|
|
365
450
|
// Apply computed fields returned from API
|
|
366
451
|
if (
|
|
@@ -413,6 +498,8 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
413
498
|
rhfForm,
|
|
414
499
|
computedFieldDependencies,
|
|
415
500
|
draftOnEveryChange,
|
|
501
|
+
isInteractiveMode,
|
|
502
|
+
draftId,
|
|
416
503
|
]
|
|
417
504
|
);
|
|
418
505
|
|
|
@@ -539,21 +626,51 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
539
626
|
recordData as Partial<T> | undefined
|
|
540
627
|
);
|
|
541
628
|
|
|
542
|
-
|
|
543
|
-
const result = await submitFormData<T>(
|
|
544
|
-
source,
|
|
545
|
-
operation,
|
|
546
|
-
cleanedData,
|
|
547
|
-
recordId
|
|
548
|
-
);
|
|
629
|
+
let result;
|
|
549
630
|
|
|
550
|
-
if (
|
|
551
|
-
|
|
631
|
+
if (isInteractiveMode) {
|
|
632
|
+
// Interactive mode submission
|
|
633
|
+
const client = api<T>(source);
|
|
634
|
+
|
|
635
|
+
if (operation === "create") {
|
|
636
|
+
// Interactive create: must have draftId
|
|
637
|
+
if (!draftId) {
|
|
638
|
+
throw new Error(
|
|
639
|
+
"Interactive create mode requires a draft ID. Draft creation may have failed."
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
// POST /{bdo_id}/draft with _id in payload
|
|
643
|
+
const response = await client.draft({
|
|
644
|
+
...cleanedData,
|
|
645
|
+
_id: draftId,
|
|
646
|
+
} as any);
|
|
647
|
+
result = { success: true, data: response };
|
|
648
|
+
} else {
|
|
649
|
+
// Interactive update: POST /{bdo_id}/{id}/draft
|
|
650
|
+
const response = await client.draftUpdate(recordId!, cleanedData);
|
|
651
|
+
result = { success: true, data: response };
|
|
652
|
+
}
|
|
653
|
+
} else {
|
|
654
|
+
// Non-interactive mode: use existing submitFormData
|
|
655
|
+
result = await submitFormData<T>(
|
|
656
|
+
source,
|
|
657
|
+
operation,
|
|
658
|
+
cleanedData,
|
|
659
|
+
recordId
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
if (!result.success) {
|
|
663
|
+
throw result.error || new Error("Submission failed");
|
|
664
|
+
}
|
|
552
665
|
}
|
|
553
666
|
|
|
554
667
|
// Reset form for create operations
|
|
555
668
|
if (operation === "create") {
|
|
556
669
|
rhfForm.reset();
|
|
670
|
+
// Clear draft state for interactive mode
|
|
671
|
+
if (isInteractiveMode) {
|
|
672
|
+
setDraftId(null);
|
|
673
|
+
}
|
|
557
674
|
}
|
|
558
675
|
|
|
559
676
|
// Success callback with response data
|
|
@@ -579,6 +696,8 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
579
696
|
operation,
|
|
580
697
|
recordId,
|
|
581
698
|
recordData,
|
|
699
|
+
isInteractiveMode,
|
|
700
|
+
draftId,
|
|
582
701
|
]
|
|
583
702
|
);
|
|
584
703
|
|
|
@@ -645,10 +764,13 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
645
764
|
// COMPUTED PROPERTIES
|
|
646
765
|
// ============================================================
|
|
647
766
|
|
|
767
|
+
// Loading state includes interactive mode draft creation
|
|
648
768
|
const isLoadingInitialData =
|
|
649
|
-
isLoadingSchema ||
|
|
769
|
+
isLoadingSchema ||
|
|
770
|
+
(operation === "update" && isLoadingRecord) ||
|
|
771
|
+
(isInteractiveMode && operation === "create" && isCreatingDraft);
|
|
650
772
|
const isLoading = isLoadingInitialData || isSubmitting;
|
|
651
|
-
const loadError = schemaError || recordError;
|
|
773
|
+
const loadError = schemaError || recordError || draftError;
|
|
652
774
|
const hasError = !!loadError;
|
|
653
775
|
|
|
654
776
|
const computedFields = useMemo<Array<keyof T>>(
|
|
@@ -809,6 +931,10 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
809
931
|
isLoadingRecord,
|
|
810
932
|
isLoading,
|
|
811
933
|
|
|
934
|
+
// Interactive mode state
|
|
935
|
+
draftId,
|
|
936
|
+
isCreatingDraft,
|
|
937
|
+
|
|
812
938
|
// Error handling
|
|
813
939
|
loadError: loadError as Error | null,
|
|
814
940
|
hasError,
|