@ram_28/kf-ai-sdk 2.0.2 → 2.0.3

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 (96) hide show
  1. package/dist/BaseField-B6da88U7.js +40 -0
  2. package/dist/BaseField-Drp0-OxL.cjs +1 -0
  3. package/dist/api/client.d.ts +7 -0
  4. package/dist/api/client.d.ts.map +1 -1
  5. package/dist/api/index.d.ts +1 -1
  6. package/dist/api/index.d.ts.map +1 -1
  7. package/dist/api.cjs +1 -1
  8. package/dist/api.mjs +3 -3
  9. package/dist/auth.cjs +1 -1
  10. package/dist/auth.mjs +2 -2
  11. package/dist/bdo/core/BaseBdo.d.ts +4 -0
  12. package/dist/bdo/core/BaseBdo.d.ts.map +1 -1
  13. package/dist/bdo.cjs +1 -1
  14. package/dist/bdo.mjs +91 -118
  15. package/dist/client-BULEEaCP.js +222 -0
  16. package/dist/client-DtPpfJc1.cjs +1 -0
  17. package/dist/components/hooks/useForm/index.d.ts +1 -1
  18. package/dist/components/hooks/useForm/index.d.ts.map +1 -1
  19. package/dist/components/hooks/useForm/types.d.ts +15 -2
  20. package/dist/components/hooks/useForm/types.d.ts.map +1 -1
  21. package/dist/components/hooks/useForm/useDraftInteraction.d.ts +26 -0
  22. package/dist/components/hooks/useForm/useDraftInteraction.d.ts.map +1 -0
  23. package/dist/components/hooks/useForm/useForm.d.ts +1 -0
  24. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
  25. package/dist/{constants-CM9xOACN.js → constants-BQrBcCON.js} +5 -5
  26. package/dist/error-handling-CAoD0Kwb.cjs +1 -0
  27. package/dist/error-handling-CrhTtD88.js +14 -0
  28. package/dist/filter.mjs +1 -1
  29. package/dist/form.cjs +1 -1
  30. package/dist/form.mjs +308 -1187
  31. package/dist/form.types.d.ts +1 -1
  32. package/dist/form.types.d.ts.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.esm-Cj63v5ny.js +1014 -0
  36. package/dist/index.esm-DuwT11sx.cjs +1 -0
  37. package/dist/{metadata-BN57S6W9.cjs → metadata-BJWukIqS.cjs} +1 -1
  38. package/dist/{metadata-P7DGCgIG.js → metadata-CJuFxytC.js} +1 -1
  39. package/dist/table.cjs +1 -1
  40. package/dist/table.mjs +83 -93
  41. package/dist/types/constants.d.ts +2 -2
  42. package/dist/types/constants.d.ts.map +1 -1
  43. package/dist/workflow/Activity.d.ts +85 -0
  44. package/dist/workflow/Activity.d.ts.map +1 -0
  45. package/dist/workflow/ActivityInstance.d.ts +96 -0
  46. package/dist/workflow/ActivityInstance.d.ts.map +1 -0
  47. package/dist/workflow/client.d.ts +39 -0
  48. package/dist/workflow/client.d.ts.map +1 -0
  49. package/dist/workflow/components/useActivityForm/createActivityItemProxy.d.ts +16 -0
  50. package/dist/workflow/components/useActivityForm/createActivityItemProxy.d.ts.map +1 -0
  51. package/dist/workflow/components/useActivityForm/createActivityResolver.d.ts +22 -0
  52. package/dist/workflow/components/useActivityForm/createActivityResolver.d.ts.map +1 -0
  53. package/dist/workflow/components/useActivityForm/index.d.ts +3 -0
  54. package/dist/workflow/components/useActivityForm/index.d.ts.map +1 -0
  55. package/dist/workflow/components/useActivityForm/types.d.ts +80 -0
  56. package/dist/workflow/components/useActivityForm/types.d.ts.map +1 -0
  57. package/dist/workflow/components/useActivityForm/useActivityForm.d.ts +4 -0
  58. package/dist/workflow/components/useActivityForm/useActivityForm.d.ts.map +1 -0
  59. package/dist/workflow/index.d.ts +8 -0
  60. package/dist/workflow/index.d.ts.map +1 -0
  61. package/dist/workflow/types.d.ts +53 -0
  62. package/dist/workflow/types.d.ts.map +1 -0
  63. package/dist/workflow.cjs +1 -0
  64. package/dist/workflow.d.ts +8 -0
  65. package/dist/workflow.d.ts.map +1 -0
  66. package/dist/workflow.mjs +565 -0
  67. package/dist/workflow.types.cjs +1 -0
  68. package/dist/workflow.types.d.ts +2 -0
  69. package/dist/workflow.types.d.ts.map +1 -0
  70. package/dist/workflow.types.mjs +1 -0
  71. package/docs/workflow.md +703 -0
  72. package/package.json +21 -1
  73. package/sdk/api/client.ts +85 -52
  74. package/sdk/api/index.ts +1 -0
  75. package/sdk/bdo/core/BaseBdo.ts +10 -0
  76. package/sdk/components/hooks/useForm/index.ts +1 -0
  77. package/sdk/components/hooks/useForm/types.ts +17 -3
  78. package/sdk/components/hooks/useForm/useDraftInteraction.ts +251 -0
  79. package/sdk/components/hooks/useForm/useForm.ts +106 -19
  80. package/sdk/form.types.ts +1 -0
  81. package/sdk/index.ts +6 -0
  82. package/sdk/types/constants.ts +2 -2
  83. package/sdk/workflow/Activity.ts +181 -0
  84. package/sdk/workflow/ActivityInstance.ts +339 -0
  85. package/sdk/workflow/client.ts +208 -0
  86. package/sdk/workflow/components/useActivityForm/createActivityItemProxy.ts +126 -0
  87. package/sdk/workflow/components/useActivityForm/createActivityResolver.ts +61 -0
  88. package/sdk/workflow/components/useActivityForm/index.ts +5 -0
  89. package/sdk/workflow/components/useActivityForm/types.ts +166 -0
  90. package/sdk/workflow/components/useActivityForm/useActivityForm.ts +386 -0
  91. package/sdk/workflow/index.ts +20 -0
  92. package/sdk/workflow/types.ts +84 -0
  93. package/sdk/workflow.ts +25 -0
  94. package/sdk/workflow.types.ts +11 -0
  95. package/dist/client-Bo-RLKJi.cjs +0 -1
  96. package/dist/client-eA4VvNTo.js +0 -178
@@ -0,0 +1,251 @@
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
+ }
@@ -10,6 +10,7 @@ import {
10
10
  import { useQuery } from "@tanstack/react-query";
11
11
  import { createResolver } from "./createResolver";
12
12
  import { createItemProxy } from "./createItemProxy";
13
+ import { useDraftInteraction } from "./useDraftInteraction";
13
14
  import { getBdoSchema } from "../../../api/metadata";
14
15
  import type { BaseBdo } from "../../../bdo";
15
16
  import type {
@@ -19,6 +20,7 @@ import type {
19
20
  AllFieldsType,
20
21
  CreatableBdo,
21
22
  UpdatableBdo,
23
+ InteractiveCreatableBdo,
22
24
  } from "./types";
23
25
 
24
26
  /**
@@ -33,6 +35,7 @@ import type {
33
35
  * - Smart register: auto-disables readonly fields
34
36
  * - Payload filtering: handleSubmit auto-filters to editable fields only
35
37
  * - Constraint validation: auto-validates required, length, etc. from field meta
38
+ * - Interactive draft mode: real-time server-side computation on field blur/change
36
39
  */
37
40
  export function useForm<B extends BaseBdo<any, any, any>>(
38
41
  options: UseFormOptionsType<B>,
@@ -43,11 +46,30 @@ export function useForm<B extends BaseBdo<any, any, any>>(
43
46
  operation: explicitOperation,
44
47
  defaultValues,
45
48
  mode = "onBlur",
46
- enableDraft: _enableDraft = false,
49
+ enableDraft,
47
50
  enableConstraintValidation,
48
51
  enableExpressionValidation,
49
52
  } = options;
50
53
 
54
+ // ============================================================
55
+ // INTERACTION MODE RESOLUTION
56
+ // ============================================================
57
+
58
+ const explicitInteractionMode = (options as any).interactionMode as
59
+ | "interactive"
60
+ | "non-interactive"
61
+ | undefined;
62
+
63
+ const interactionMode =
64
+ explicitInteractionMode ??
65
+ (enableDraft === true
66
+ ? "interactive"
67
+ : enableDraft === false
68
+ ? "non-interactive"
69
+ : "interactive");
70
+
71
+ const isInteractive = interactionMode === "interactive";
72
+
51
73
  // Infer operation from recordId if not explicitly provided
52
74
  const operation = explicitOperation ?? (recordId ? "update" : "create");
53
75
 
@@ -114,6 +136,31 @@ export function useForm<B extends BaseBdo<any, any, any>>(
114
136
  operation === "update" && record ? (record as FieldValues) : undefined,
115
137
  });
116
138
 
139
+ // ============================================================
140
+ // FIELD DEFINITIONS
141
+ // ============================================================
142
+
143
+ const fields = bdo.getFields();
144
+
145
+ // ============================================================
146
+ // DRAFT INTERACTION
147
+ // ============================================================
148
+
149
+ const {
150
+ draftId,
151
+ isInitializingDraft,
152
+ isInteracting,
153
+ interactionError,
154
+ triggerInteraction,
155
+ commitDraft,
156
+ } = useDraftInteraction({
157
+ bdo: bdo as unknown as InteractiveCreatableBdo,
158
+ form,
159
+ mode,
160
+ fields,
161
+ enabled: isInteractive && operation === "create",
162
+ });
163
+
117
164
  // ============================================================
118
165
  // ITEM PROXY
119
166
  // ============================================================
@@ -125,11 +172,9 @@ export function useForm<B extends BaseBdo<any, any, any>>(
125
172
  );
126
173
 
127
174
  // ============================================================
128
- // SMART REGISTER (auto-disables readonly fields)
175
+ // SMART REGISTER (auto-disables readonly fields + interaction trigger)
129
176
  // ============================================================
130
177
 
131
- const fields = bdo.getFields();
132
-
133
178
  const smartRegister = useCallback(
134
179
  (name: string, registerOptions?: RegisterOptions) => {
135
180
  const rhfResult = form.register(name as any, registerOptions);
@@ -139,11 +184,44 @@ export function useForm<B extends BaseBdo<any, any, any>>(
139
184
  return { ...rhfResult, disabled: true };
140
185
  }
141
186
 
187
+ // In interactive mode, wrap onBlur for blur-triggered interaction
188
+ if (
189
+ isInteractive &&
190
+ (mode === "onBlur" || mode === "onTouched" || mode === "all")
191
+ ) {
192
+ const originalOnBlur = rhfResult.onBlur;
193
+ return {
194
+ ...rhfResult,
195
+ onBlur: async (e: React.FocusEvent) => {
196
+ await originalOnBlur(e); // RHF validation first
197
+ triggerInteraction(); // then server interaction
198
+ },
199
+ };
200
+ }
201
+
142
202
  return rhfResult;
143
203
  },
144
- [form, fields],
204
+ [form, fields, isInteractive, mode, triggerInteraction],
145
205
  );
146
206
 
207
+ // ============================================================
208
+ // WATCH SUBSCRIPTION (for onChange/all mode interaction)
209
+ // ============================================================
210
+
211
+ useEffect(() => {
212
+ if (!isInteractive || (mode !== "onChange" && mode !== "all")) return;
213
+
214
+ const subscription = form.watch((_value, { type }) => {
215
+ // RHF fires with type: "change" for user input,
216
+ // type: undefined for programmatic setValue — prevents re-trigger loops
217
+ if (type === "change") {
218
+ triggerInteraction(); // debounced inside useDraftInteraction
219
+ }
220
+ });
221
+
222
+ return () => subscription.unsubscribe();
223
+ }, [isInteractive, mode, form, triggerInteraction]);
224
+
147
225
  // ============================================================
148
226
  // CUSTOM HANDLE SUBMIT (with API call + payload filtering)
149
227
  // ============================================================
@@ -155,31 +233,34 @@ export function useForm<B extends BaseBdo<any, any, any>>(
155
233
  async (data, e) => {
156
234
  try {
157
235
  const filteredData: Record<string, unknown> = {};
236
+ let result: unknown;
158
237
 
159
- if (operation === "create") {
160
- // Create mode - send all known, non-readonly fields
238
+ if (isInteractive && operation === "create") {
239
+ // Interactive create - send only dirty, non-readonly fields
240
+ // (record already exists from init, so only send changes)
241
+ const dirtyFields = form.formState.dirtyFields;
242
+ for (const [key, value] of Object.entries(data)) {
243
+ if (fields[key] && !fields[key].readOnly && dirtyFields[key]) {
244
+ filteredData[key] = value;
245
+ }
246
+ }
247
+ result = await commitDraft(filteredData);
248
+ } else if (operation === "create") {
249
+ // Non-interactive create - send all known, non-readonly fields
161
250
  for (const [key, value] of Object.entries(data)) {
162
251
  if (fields[key] && !fields[key].readOnly) {
163
252
  filteredData[key] = value;
164
253
  }
165
254
  }
255
+ result = await (bdo as unknown as CreatableBdo).create(filteredData);
166
256
  } else {
167
- // Update mode - send only known, non-readonly, dirty fields
257
+ // Update (always non-interactive) - send only dirty, non-readonly fields
168
258
  const dirtyFields = form.formState.dirtyFields;
169
259
  for (const [key, value] of Object.entries(data)) {
170
260
  if (fields[key] && !fields[key].readOnly && dirtyFields[key]) {
171
261
  filteredData[key] = value;
172
262
  }
173
263
  }
174
- }
175
-
176
- let result: unknown;
177
-
178
- if (operation === "create") {
179
- // Safe: create operation requires CreatableBdo (enforced by UseFormOptionsType)
180
- result = await (bdo as unknown as CreatableBdo).create(filteredData);
181
- } else {
182
- // Safe: update operation requires UpdatableBdo (enforced by UseFormOptionsType)
183
264
  result = await (bdo as unknown as UpdatableBdo).update(recordId!, filteredData);
184
265
  }
185
266
 
@@ -196,7 +277,7 @@ export function useForm<B extends BaseBdo<any, any, any>>(
196
277
  },
197
278
  );
198
279
  },
199
- [form, bdo, operation, recordId, fields],
280
+ [form, bdo, operation, recordId, fields, isInteractive, commitDraft],
200
281
  );
201
282
 
202
283
  // ============================================================
@@ -236,10 +317,16 @@ export function useForm<B extends BaseBdo<any, any, any>>(
236
317
  dirtyFields: form.formState.dirtyFields as any,
237
318
 
238
319
  // Loading states
239
- isLoading: isLoadingRecord,
320
+ isLoading: isLoadingRecord || isInitializingDraft,
240
321
  isFetching: isFetchingRecord,
241
322
 
242
323
  // Error
243
324
  loadError: recordError as Error | null,
325
+
326
+ // Draft / Interactive mode
327
+ draftId,
328
+ isInitializingDraft,
329
+ isInteracting,
330
+ interactionError,
244
331
  };
245
332
  }
package/sdk/form.types.ts CHANGED
@@ -14,4 +14,5 @@ export type {
14
14
  ExtractEditableType,
15
15
  ExtractReadonlyType,
16
16
  AllFieldsType,
17
+ InteractiveCreatableBdo,
17
18
  } from "./components/hooks/useForm";
package/sdk/index.ts CHANGED
@@ -40,6 +40,12 @@ export type {
40
40
 
41
41
  export { api, setApiBaseUrl, getApiBaseUrl, getBdoSchema } from './api';
42
42
 
43
+ // ============================================================
44
+ // WORKFLOW CLIENT - Business Process / Activity operations
45
+ // ============================================================
46
+
47
+ export { Workflow, useActivityForm } from './workflow';
48
+
43
49
  // ============================================================
44
50
  // TYPES - Core type definitions
45
51
  // ============================================================
@@ -211,9 +211,9 @@ export const FormOperation = {
211
211
  * });
212
212
  */
213
213
  export const InteractionMode = {
214
- /** Real-time server-side validation and computation on every field blur */
214
+ /** Real-time server-side validation and computation on field blur/change */
215
215
  Interactive: "interactive",
216
- /** Draft only for computed field dependencies */
216
+ /** No server interaction during editing submit only */
217
217
  NonInteractive: "non-interactive",
218
218
  } as const;
219
219
 
@@ -0,0 +1,181 @@
1
+ // ============================================================
2
+ // ACTIVITY BASE CLASS
3
+ // ============================================================
4
+ // Lean abstract base class for typed workflow activities.
5
+ // Replaces ActivityForm — no public getFields(), no phantom types,
6
+ // no exported meta type.
7
+ //
8
+ // Each activity subclass defines:
9
+ // - meta: { businessProcessId, activityId }
10
+ // - Typed field instances as class properties
11
+ //
12
+ // Methods:
13
+ // activity.getInstanceList(options) // list activity instances
14
+ // activity.instanceMetrics(options) // get aggregated metrics
15
+ // activity.getInstance(instanceId) // get typed ActivityInstance
16
+
17
+ import { Workflow } from "./client";
18
+ import { createActivityInstance } from "./ActivityInstance";
19
+ import type { ActivityInstanceType } from "./ActivityInstance";
20
+ import type { ActivityInstanceFieldsType, ActivityOperations } from "./types";
21
+ import type {
22
+ ListOptionsType,
23
+ ListResponseType,
24
+ MetricOptionsType,
25
+ MetricResponseType,
26
+ } from "../types/common";
27
+ import { BaseField } from "../bdo/fields/BaseField";
28
+
29
+ // ============================================================
30
+ // ABSTRACT BASE CLASS
31
+ // ============================================================
32
+
33
+ /**
34
+ * Abstract base class for workflow activities.
35
+ *
36
+ * Extend this class to define typed activity input fields.
37
+ * Each activity is a typed class with field definitions and data operations.
38
+ *
39
+ * @template TEntity - The full entity type with all fields
40
+ * @template TEditable - Fields the user can edit
41
+ * @template TReadonly - Fields that are read-only (computed / system)
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * class EmployeeInputActivity extends Activity<
46
+ * EmployeeInputEntityType,
47
+ * EmployeeInputEditable,
48
+ * EmployeeInputReadonly
49
+ * > {
50
+ * readonly meta = {
51
+ * businessProcessId: "SimpleLeaveProcess",
52
+ * activityId: "EMPLOYEE_INPUT",
53
+ * };
54
+ *
55
+ * readonly StartDate = new DateTimeField({ id: "StartDate", label: "Start Date" });
56
+ * readonly EndDate = new DateTimeField({ id: "EndDate", label: "End Date" });
57
+ * readonly LeaveDays = new NumberField({ id: "LeaveDays", label: "Leave Days", editable: false });
58
+ * }
59
+ *
60
+ * const activity = new EmployeeInputActivity();
61
+ * const items = await activity.getInstanceList({ Page: 1, PageSize: 10 });
62
+ * const instance = await activity.getInstance("inst_123");
63
+ * instance.StartDate.get(); // typed value
64
+ * instance.StartDate.set("2026-03-01");
65
+ * await instance.update({ StartDate: "2026-03-01" });
66
+ * ```
67
+ */
68
+ export abstract class Activity<
69
+ TEntity extends Record<string, unknown>,
70
+ TEditable extends Record<string, unknown> = TEntity,
71
+ // eslint-disable-next-line @typescript-eslint/ban-types
72
+ TReadonly extends Record<string, unknown> = {},
73
+ > {
74
+ /**
75
+ * Activity metadata — identifies the business process and activity
76
+ */
77
+ abstract readonly meta: {
78
+ readonly businessProcessId: string;
79
+ readonly activityId: string;
80
+ };
81
+
82
+ // ============================================================
83
+ // ACTIVITY OPERATIONS (internal)
84
+ // ============================================================
85
+
86
+ /**
87
+ * Get ActivityOperations for this activity.
88
+ * @internal
89
+ */
90
+ private _ops(): ActivityOperations<TEntity> {
91
+ return new Workflow<TEntity>(this.meta.businessProcessId)
92
+ .activity(this.meta.activityId);
93
+ }
94
+
95
+ // ============================================================
96
+ // FIELD DISCOVERY (internal)
97
+ // ============================================================
98
+
99
+ private _fieldsCache: Record<string, BaseField<unknown>> | null = null;
100
+
101
+ /**
102
+ * Discover BaseField instances from subclass properties.
103
+ * @internal
104
+ */
105
+ private _discoverFields(): Record<string, BaseField<unknown>> {
106
+ if (this._fieldsCache) return this._fieldsCache;
107
+
108
+ const fields: Record<string, BaseField<unknown>> = {};
109
+ for (const key of Object.keys(this)) {
110
+ const value = (this as Record<string, unknown>)[key];
111
+ if (value instanceof BaseField) {
112
+ fields[key] = value;
113
+ }
114
+ }
115
+
116
+ this._fieldsCache = fields;
117
+ return fields;
118
+ }
119
+
120
+ // ============================================================
121
+ // PUBLIC METHODS
122
+ // ============================================================
123
+
124
+ /**
125
+ * List activity instances with optional filtering/pagination.
126
+ */
127
+ async getInstanceList(
128
+ options?: ListOptionsType,
129
+ ): Promise<ListResponseType<ActivityInstanceFieldsType & TEntity>> {
130
+ return this._ops().list(options);
131
+ }
132
+
133
+ /**
134
+ * Get aggregated metrics for activity instances.
135
+ */
136
+ async instanceMetrics(
137
+ options: Omit<MetricOptionsType, "Type">,
138
+ ): Promise<MetricResponseType> {
139
+ return this._ops().metric(options);
140
+ }
141
+
142
+ /**
143
+ * Get a typed ActivityInstance with field accessors and persistence methods.
144
+ *
145
+ * @param instanceId - The activity instance identifier
146
+ * @returns ActivityInstance with typed field accessors
147
+ */
148
+ async getInstance(
149
+ instanceId: string,
150
+ ): Promise<ActivityInstanceType<TEntity, TEditable, TReadonly>> {
151
+ const ops = this._ops();
152
+ const data = await ops.read(instanceId);
153
+ const fields = this._discoverFields();
154
+ return createActivityInstance<TEntity, TEditable, TReadonly>(
155
+ ops,
156
+ instanceId,
157
+ data as TEntity,
158
+ fields,
159
+ );
160
+ }
161
+
162
+ // ============================================================
163
+ // INTERNAL ACCESSORS (used by useActivityForm hook)
164
+ // ============================================================
165
+
166
+ /**
167
+ * Get discovered fields — used internally by useActivityForm hook.
168
+ * @internal
169
+ */
170
+ _getFields(): Record<string, BaseField<unknown>> {
171
+ return this._discoverFields();
172
+ }
173
+
174
+ /**
175
+ * Get ActivityOperations — used internally by useActivityForm hook.
176
+ * @internal
177
+ */
178
+ _getOps(): ActivityOperations<TEntity> {
179
+ return this._ops();
180
+ }
181
+ }