@ram_28/kf-ai-sdk 2.0.14 → 2.0.15

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 (119) hide show
  1. package/README.md +2 -1
  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.cjs +1 -1
  9. package/dist/bdo.mjs +228 -472
  10. package/dist/{client-DnO2KKrw.cjs → client-D5k4SYuw.cjs} +1 -1
  11. package/dist/{client-iQTqFDNI.js → client-_ayziI1d.js} +33 -32
  12. package/dist/components/hooks/index.d.ts +9 -3
  13. package/dist/components/hooks/index.d.ts.map +1 -1
  14. package/dist/{workflow/components → components/hooks}/useActivityForm/createActivityItemProxy.d.ts +9 -5
  15. package/dist/components/hooks/useActivityForm/createActivityItemProxy.d.ts.map +1 -0
  16. package/dist/components/hooks/useActivityForm/createActivityResolver.d.ts +23 -0
  17. package/dist/components/hooks/useActivityForm/createActivityResolver.d.ts.map +1 -0
  18. package/dist/components/hooks/useActivityForm/index.d.ts.map +1 -0
  19. package/dist/{workflow/components → components/hooks}/useActivityForm/types.d.ts +11 -7
  20. package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -0
  21. package/dist/{workflow/components → components/hooks}/useActivityForm/useActivityForm.d.ts +2 -2
  22. package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -0
  23. package/dist/components/hooks/useActivityTable/index.d.ts +4 -0
  24. package/dist/components/hooks/useActivityTable/index.d.ts.map +1 -0
  25. package/dist/components/hooks/useActivityTable/types.d.ts +36 -0
  26. package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -0
  27. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts +4 -0
  28. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -0
  29. package/dist/components/hooks/useBDOTable/index.d.ts +3 -0
  30. package/dist/components/hooks/useBDOTable/index.d.ts.map +1 -0
  31. package/dist/components/hooks/useBDOTable/types.d.ts +26 -0
  32. package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -0
  33. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +3 -0
  34. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -0
  35. package/dist/components/hooks/useTable/index.d.ts +2 -2
  36. package/dist/components/hooks/useTable/index.d.ts.map +1 -1
  37. package/dist/components/hooks/useTable/types.d.ts +11 -10
  38. package/dist/components/hooks/useTable/types.d.ts.map +1 -1
  39. package/dist/components/hooks/useTable/useTable.d.ts +1 -1
  40. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
  41. package/dist/createResolver-AIgUwoS6.cjs +1 -0
  42. package/dist/createResolver-ZHXQ7QMa.js +1078 -0
  43. package/dist/form.cjs +1 -1
  44. package/dist/form.mjs +252 -314
  45. package/dist/{metadata-DpfI3zRN.js → metadata-Cc1mBcLS.js} +1 -1
  46. package/dist/{metadata-DgLSJkF5.cjs → metadata-DWXQPDav.cjs} +1 -1
  47. package/dist/table.cjs +1 -1
  48. package/dist/table.d.ts +1 -0
  49. package/dist/table.d.ts.map +1 -1
  50. package/dist/table.mjs +16 -192
  51. package/dist/table.types.d.ts +2 -1
  52. package/dist/table.types.d.ts.map +1 -1
  53. package/dist/types/base-fields.d.ts +4 -4
  54. package/dist/types/base-fields.d.ts.map +1 -1
  55. package/dist/useTable-CeRklbdT.cjs +1 -0
  56. package/dist/useTable-DS0-WInw.js +203 -0
  57. package/dist/workflow/Activity.d.ts +9 -9
  58. package/dist/workflow/Activity.d.ts.map +1 -1
  59. package/dist/workflow/client.d.ts.map +1 -1
  60. package/dist/workflow/createFieldFromMeta.d.ts +29 -0
  61. package/dist/workflow/createFieldFromMeta.d.ts.map +1 -0
  62. package/dist/workflow/index.d.ts +1 -2
  63. package/dist/workflow/index.d.ts.map +1 -1
  64. package/dist/workflow/types.d.ts +12 -12
  65. package/dist/workflow/types.d.ts.map +1 -1
  66. package/dist/workflow.cjs +1 -1
  67. package/dist/workflow.d.ts +5 -2
  68. package/dist/workflow.d.ts.map +1 -1
  69. package/dist/workflow.mjs +716 -338
  70. package/dist/workflow.types.d.ts +1 -0
  71. package/dist/workflow.types.d.ts.map +1 -1
  72. package/docs/gaps.md +410 -0
  73. package/docs/useActivityTable.md +481 -0
  74. package/docs/useBDOTable.md +317 -0
  75. package/docs/workflow.md +143 -34
  76. package/package.json +1 -1
  77. package/sdk/bdo/fields/UserField.ts +1 -1
  78. package/sdk/components/hooks/index.ts +28 -5
  79. package/sdk/components/hooks/useActivityForm/createActivityItemProxy.ts +400 -0
  80. package/sdk/components/hooks/useActivityForm/createActivityResolver.ts +87 -0
  81. package/sdk/{workflow/components → components/hooks}/useActivityForm/types.ts +21 -8
  82. package/sdk/components/hooks/useActivityForm/useActivityForm.ts +628 -0
  83. package/sdk/components/hooks/useActivityTable/index.ts +8 -0
  84. package/sdk/components/hooks/useActivityTable/types.ts +45 -0
  85. package/sdk/components/hooks/useActivityTable/useActivityTable.ts +71 -0
  86. package/sdk/components/hooks/useBDOTable/index.ts +2 -0
  87. package/sdk/components/hooks/useBDOTable/types.ts +24 -0
  88. package/sdk/components/hooks/useBDOTable/useBDOTable.ts +15 -0
  89. package/sdk/components/hooks/useTable/index.ts +3 -3
  90. package/sdk/components/hooks/useTable/types.ts +16 -12
  91. package/sdk/components/hooks/useTable/useTable.ts +56 -49
  92. package/sdk/table.ts +4 -1
  93. package/sdk/table.types.ts +7 -4
  94. package/sdk/types/base-fields.ts +4 -4
  95. package/sdk/workflow/Activity.ts +14 -13
  96. package/sdk/workflow/client.ts +21 -8
  97. package/sdk/workflow/createFieldFromMeta.ts +110 -0
  98. package/sdk/workflow/index.ts +1 -6
  99. package/sdk/workflow/types.ts +13 -12
  100. package/sdk/workflow.ts +11 -2
  101. package/sdk/workflow.types.ts +7 -0
  102. package/dist/BaseField-B6da88U7.js +0 -40
  103. package/dist/BaseField-Drp0-OxL.cjs +0 -1
  104. package/dist/error-handling-CAoD0Kwb.cjs +0 -1
  105. package/dist/error-handling-CrhTtD88.js +0 -14
  106. package/dist/index.esm-Cj63v5ny.js +0 -1014
  107. package/dist/index.esm-DuwT11sx.cjs +0 -1
  108. package/dist/workflow/components/useActivityForm/createActivityItemProxy.d.ts.map +0 -1
  109. package/dist/workflow/components/useActivityForm/createActivityResolver.d.ts +0 -22
  110. package/dist/workflow/components/useActivityForm/createActivityResolver.d.ts.map +0 -1
  111. package/dist/workflow/components/useActivityForm/index.d.ts.map +0 -1
  112. package/dist/workflow/components/useActivityForm/types.d.ts.map +0 -1
  113. package/dist/workflow/components/useActivityForm/useActivityForm.d.ts.map +0 -1
  114. package/docs/useTable.md +0 -369
  115. package/sdk/workflow/components/useActivityForm/createActivityItemProxy.ts +0 -130
  116. package/sdk/workflow/components/useActivityForm/createActivityResolver.ts +0 -61
  117. package/sdk/workflow/components/useActivityForm/useActivityForm.ts +0 -386
  118. /package/dist/{workflow/components → components/hooks}/useActivityForm/index.d.ts +0 -0
  119. /package/sdk/{workflow/components → components/hooks}/useActivityForm/index.ts +0 -0
@@ -1,10 +1,33 @@
1
- // Table hook
2
- export { useTable } from "./useTable";
1
+ // Table hook (base)
2
+ export { useTable } from './useTable';
3
3
  export type {
4
4
  UseTableOptionsType,
5
5
  UseTableReturnType,
6
- ColumnDefinitionType,
7
- } from "./useTable";
6
+ PaginationStateType,
7
+ } from './useTable';
8
+
9
+ // BDO table hook
10
+ export { useBDOTable } from './useBDOTable';
11
+ export type {
12
+ UseBDOTableOptionsType,
13
+ UseBDOTableReturnType,
14
+ } from './useBDOTable';
15
+
16
+ // Activity table hook
17
+ export { useActivityTable, ActivityTableStatus } from './useActivityTable';
18
+ export type {
19
+ UseActivityTableOptionsType,
20
+ UseActivityTableReturnType,
21
+ ActivityTableStatusType,
22
+ ActivityRowType,
23
+ } from './useActivityTable';
24
+
25
+ // Activity form hook
26
+ export { useActivityForm } from './useActivityForm';
27
+ export type {
28
+ UseActivityFormOptions,
29
+ UseActivityFormReturn,
30
+ } from './useActivityForm';
8
31
 
9
32
  // Filter hook
10
- export * from "./useFilter";
33
+ export * from './useFilter';
@@ -0,0 +1,400 @@
1
+ // ============================================================
2
+ // ACTIVITY ITEM PROXY
3
+ // ============================================================
4
+ // Proxy-based item that delegates to RHF for state management.
5
+ // Follows the same pattern as createItemProxy for BaseBdo,
6
+ // adapted for Activity. Attachment methods call
7
+ // createResourceClient() directly (matching BDO's api() pattern).
8
+
9
+ import type { UseFormReturn, Path, FieldValues } from 'react-hook-form';
10
+ import type { BaseFieldMetaType } from '../../../bdo/core/types';
11
+ import type { BaseField } from '../../../bdo/fields/BaseField';
12
+ import type { FileType } from '../../../types/base-fields';
13
+ import type {
14
+ FileDownloadResponseType,
15
+ AttachmentViewType,
16
+ } from '../../../types/common';
17
+ import type {
18
+ FormItemType,
19
+ EditableFormFieldAccessorType,
20
+ ReadonlyFormFieldAccessorType,
21
+ } from '../useForm/types';
22
+ import type { Activity } from '../../../workflow/Activity';
23
+ import type {
24
+ ExtractActivityEditable,
25
+ ExtractActivityReadonly,
26
+ } from './types';
27
+ import { createResourceClient } from '../../../api/client';
28
+ import {
29
+ validateFileExtension,
30
+ extractFileExtension,
31
+ } from '../../../bdo/fields/attachment-constants';
32
+
33
+ /**
34
+ * Creates a Proxy-based Item that delegates to RHF for state management.
35
+ *
36
+ * Key principle: Item has NO state. It's a view over RHF's state.
37
+ * Editable fields get set(), readonly fields do not.
38
+ *
39
+ * Activity forms always have an instance ID (no draft creation needed),
40
+ * so attachment operations use the provided instanceId directly.
41
+ *
42
+ * @param activity - The Activity instance for field metadata
43
+ * @param form - The RHF useForm return object
44
+ * @param instanceId - The activity instance ID
45
+ * @returns FormItemType proxy
46
+ */
47
+ export function createActivityItemProxy<A extends Activity<any, any, any>>(
48
+ activity: A,
49
+ form: UseFormReturn<FieldValues>,
50
+ instanceId: string,
51
+ ): FormItemType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>> {
52
+ const fields = activity._getFields();
53
+ const accessorCache = new Map<
54
+ string,
55
+ | EditableFormFieldAccessorType<unknown>
56
+ | ReadonlyFormFieldAccessorType<unknown>
57
+ >();
58
+
59
+ // Resource client for attachment operations — same pattern as BDO's api(boId)
60
+ const activityBasePath = `/api/app/process/${activity.meta.businessProcessId}/${activity.meta.activityId}`;
61
+ const activityApi = createResourceClient(activityBasePath);
62
+
63
+ /**
64
+ * Defensive helper — throws if no instance ID is available.
65
+ * Activity forms always have an instance, so this is a safety net.
66
+ */
67
+ function requireInstanceId(): string {
68
+ if (!instanceId) {
69
+ throw new Error(
70
+ 'Cannot perform attachment operation: no activity instance ID',
71
+ );
72
+ }
73
+ return instanceId;
74
+ }
75
+
76
+ return new Proxy(
77
+ {} as FormItemType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>>,
78
+ {
79
+ get(_, prop: string | symbol) {
80
+ // Handle symbol properties (e.g., Symbol.toStringTag)
81
+ if (typeof prop === 'symbol') {
82
+ return undefined;
83
+ }
84
+
85
+ // Direct _id access (not an accessor, just the value)
86
+ if (prop === '_id') {
87
+ return instanceId;
88
+ }
89
+
90
+ // toJSON returns all form values as plain object
91
+ if (prop === 'toJSON') {
92
+ return () => form.getValues();
93
+ }
94
+
95
+ // validate triggers RHF validation for all fields
96
+ if (prop === 'validate') {
97
+ return () => form.trigger();
98
+ }
99
+
100
+ // Return cached accessor if available
101
+ if (accessorCache.has(prop)) {
102
+ return accessorCache.get(prop);
103
+ }
104
+
105
+ // Field accessor
106
+ const bdoField = fields[prop] as BaseField<unknown> | undefined;
107
+ const fieldMeta: BaseFieldMetaType = bdoField?.meta ?? {
108
+ _id: prop,
109
+ Name: prop,
110
+ Type: 'String',
111
+ };
112
+ const isReadOnly = bdoField?.readOnly ?? false;
113
+
114
+ // Base validate function
115
+ const validate = () => {
116
+ if (bdoField) {
117
+ return bdoField.validate(
118
+ form.getValues(prop as Path<FieldValues>),
119
+ );
120
+ }
121
+ return { valid: true, errors: [] };
122
+ };
123
+
124
+ // Shared getOrDefault helper
125
+ const getOrDefault = (fallback: unknown) => {
126
+ const value = form.getValues(prop as Path<FieldValues>);
127
+ return value !== undefined && value !== null ? value : fallback;
128
+ };
129
+
130
+ // Only add set() for editable fields
131
+ if (!isReadOnly) {
132
+ // Defensive get(): File fields return [] instead of null/undefined
133
+ const fieldGet = () => {
134
+ const val = form.getValues(prop as Path<FieldValues>);
135
+ if (fieldMeta.Type === 'File') return val ?? [];
136
+ return val;
137
+ };
138
+
139
+ const accessor: EditableFormFieldAccessorType<unknown> = {
140
+ label: bdoField?.label ?? prop,
141
+ required: bdoField?.required ?? false,
142
+ readOnly: false,
143
+ defaultValue: bdoField?.defaultValue,
144
+ meta: fieldMeta,
145
+ get: fieldGet,
146
+ getOrDefault,
147
+ set: (value: unknown) => {
148
+ form.setValue(prop as Path<FieldValues>, value as any, {
149
+ shouldDirty: true,
150
+ shouldTouch: true,
151
+ shouldValidate: false,
152
+ });
153
+ },
154
+ validate,
155
+ };
156
+
157
+ // Enrich Image/File field accessors with attachment methods
158
+ if (fieldMeta.Type === 'Image' || fieldMeta.Type === 'File') {
159
+ if (fieldMeta.Type === 'Image') {
160
+ // Image: single file upload
161
+ (accessor as any).upload = async (
162
+ file: File,
163
+ ): Promise<FileType> => {
164
+ validateFileExtension(file.name, 'Image');
165
+ const id = requireInstanceId();
166
+
167
+ const [uploadInfo] = await activityApi.getUploadUrl(
168
+ id,
169
+ prop,
170
+ [
171
+ {
172
+ FileName: file.name,
173
+ Size: file.size,
174
+ FileExtension: extractFileExtension(file.name),
175
+ },
176
+ ],
177
+ );
178
+ await fetch(uploadInfo.UploadUrl.URL, {
179
+ method: 'PUT',
180
+ headers: { 'Content-Type': uploadInfo.ContentType },
181
+ body: file,
182
+ });
183
+ const metadata: FileType = {
184
+ _id: uploadInfo._id,
185
+ _name: uploadInfo._name,
186
+ FileName: uploadInfo.FileName,
187
+ FileExtension: uploadInfo.FileExtension,
188
+ Size: uploadInfo.Size,
189
+ ContentType: uploadInfo.ContentType,
190
+ };
191
+ form.setValue(prop as Path<FieldValues>, metadata as any, {
192
+ shouldDirty: true,
193
+ });
194
+ return metadata;
195
+ };
196
+
197
+ (accessor as any).deleteAttachment =
198
+ async (): Promise<void> => {
199
+ const val = form.getValues(
200
+ prop as Path<FieldValues>,
201
+ ) as any;
202
+ const id = requireInstanceId();
203
+ if (!val?._id)
204
+ throw new Error(`${prop} has no image to delete`);
205
+ await activityApi.deleteAttachment(id, prop, val._id);
206
+ form.setValue(prop as Path<FieldValues>, null as any, {
207
+ shouldDirty: true,
208
+ });
209
+ };
210
+
211
+ (accessor as any).getDownloadUrl = async (
212
+ viewType?: AttachmentViewType,
213
+ ): Promise<FileDownloadResponseType> => {
214
+ const val = form.getValues(
215
+ prop as Path<FieldValues>,
216
+ ) as any;
217
+ const id = requireInstanceId();
218
+ if (!val?._id)
219
+ throw new Error(`${prop} has no image`);
220
+ return activityApi.getDownloadUrl(
221
+ id,
222
+ prop,
223
+ val._id,
224
+ viewType,
225
+ );
226
+ };
227
+ } else {
228
+ // File field — multi-file
229
+ (accessor as any).upload = async (
230
+ files: File[],
231
+ ): Promise<FileType[]> => {
232
+ for (const file of files)
233
+ validateFileExtension(file.name, 'File');
234
+ const id = requireInstanceId();
235
+
236
+ const requests = files.map((file) => ({
237
+ FileName: file.name,
238
+ Size: file.size,
239
+ FileExtension: extractFileExtension(file.name),
240
+ }));
241
+ const uploadInfos = await activityApi.getUploadUrl(
242
+ id,
243
+ prop,
244
+ requests,
245
+ );
246
+ const uploaded: FileType[] = await Promise.all(
247
+ files.map(async (file, i) => {
248
+ await fetch(uploadInfos[i].UploadUrl.URL, {
249
+ method: 'PUT',
250
+ headers: {
251
+ 'Content-Type': uploadInfos[i].ContentType,
252
+ },
253
+ body: file,
254
+ });
255
+ return {
256
+ _id: uploadInfos[i]._id,
257
+ _name: uploadInfos[i]._name,
258
+ FileName: uploadInfos[i].FileName,
259
+ FileExtension: uploadInfos[i].FileExtension,
260
+ Size: uploadInfos[i].Size,
261
+ ContentType: uploadInfos[i].ContentType,
262
+ };
263
+ }),
264
+ );
265
+ const current =
266
+ (form.getValues(prop as Path<FieldValues>) as
267
+ | FileType[]
268
+ | undefined) ?? [];
269
+ form.setValue(
270
+ prop as Path<FieldValues>,
271
+ [...current, ...uploaded] as any,
272
+ { shouldDirty: true },
273
+ );
274
+ return uploaded;
275
+ };
276
+
277
+ (accessor as any).deleteAttachment = async (
278
+ attachmentId: string,
279
+ ): Promise<void> => {
280
+ const current =
281
+ (form.getValues(prop as Path<FieldValues>) as any[]) ?? [];
282
+ const id = requireInstanceId();
283
+ await activityApi.deleteAttachment(id, prop, attachmentId);
284
+ form.setValue(
285
+ prop as Path<FieldValues>,
286
+ current.filter((f) => f._id !== attachmentId) as any,
287
+ { shouldDirty: true },
288
+ );
289
+ };
290
+
291
+ (accessor as any).getDownloadUrl = async (
292
+ attachmentId: string,
293
+ viewType?: AttachmentViewType,
294
+ ): Promise<FileDownloadResponseType> => {
295
+ const id = requireInstanceId();
296
+ return activityApi.getDownloadUrl(
297
+ id,
298
+ prop,
299
+ attachmentId,
300
+ viewType,
301
+ );
302
+ };
303
+
304
+ (accessor as any).getDownloadUrls = async (
305
+ viewType?: AttachmentViewType,
306
+ ): Promise<FileDownloadResponseType[]> => {
307
+ const id = requireInstanceId();
308
+ return activityApi.getDownloadUrls(id, prop, viewType);
309
+ };
310
+ }
311
+ }
312
+
313
+ accessorCache.set(prop, accessor);
314
+ return accessor;
315
+ }
316
+
317
+ // Defensive get() for readonly accessor too
318
+ const readonlyGet = () => {
319
+ const val = form.getValues(prop as Path<FieldValues>);
320
+ if (fieldMeta.Type === 'File') return val ?? [];
321
+ return val;
322
+ };
323
+
324
+ const accessor: ReadonlyFormFieldAccessorType<unknown> = {
325
+ label: bdoField?.label ?? prop,
326
+ required: bdoField?.required ?? false,
327
+ readOnly: true,
328
+ defaultValue: bdoField?.defaultValue,
329
+ meta: fieldMeta,
330
+ get: readonlyGet,
331
+ getOrDefault,
332
+ validate,
333
+ };
334
+
335
+ // Enrich readonly Image/File field accessors with download methods
336
+ if (fieldMeta.Type === 'Image' || fieldMeta.Type === 'File') {
337
+ if (fieldMeta.Type === 'Image') {
338
+ (accessor as any).getDownloadUrl = async (
339
+ viewType?: AttachmentViewType,
340
+ ): Promise<FileDownloadResponseType> => {
341
+ const val = form.getValues(
342
+ prop as Path<FieldValues>,
343
+ ) as any;
344
+ const id = requireInstanceId();
345
+ if (!val?._id)
346
+ throw new Error(`${prop} has no image to download`);
347
+ return activityApi.getDownloadUrl(
348
+ id,
349
+ prop,
350
+ val._id,
351
+ viewType,
352
+ );
353
+ };
354
+ } else {
355
+ (accessor as any).getDownloadUrl = async (
356
+ attachmentId: string,
357
+ viewType?: AttachmentViewType,
358
+ ): Promise<FileDownloadResponseType> => {
359
+ const id = requireInstanceId();
360
+ return activityApi.getDownloadUrl(
361
+ id,
362
+ prop,
363
+ attachmentId,
364
+ viewType,
365
+ );
366
+ };
367
+ (accessor as any).getDownloadUrls = async (
368
+ viewType?: AttachmentViewType,
369
+ ): Promise<FileDownloadResponseType[]> => {
370
+ const id = requireInstanceId();
371
+ return activityApi.getDownloadUrls(id, prop, viewType);
372
+ };
373
+ }
374
+ }
375
+
376
+ accessorCache.set(prop, accessor);
377
+ return accessor;
378
+ },
379
+
380
+ has(_, prop) {
381
+ if (typeof prop === 'symbol') return false;
382
+ if (prop === '_id' || prop === 'toJSON' || prop === 'validate')
383
+ return true;
384
+ return prop in fields;
385
+ },
386
+
387
+ ownKeys(_) {
388
+ return [...Object.keys(fields), '_id', 'toJSON', 'validate'];
389
+ },
390
+
391
+ getOwnPropertyDescriptor(_, prop) {
392
+ if (typeof prop === 'symbol') return undefined;
393
+ return {
394
+ configurable: true,
395
+ enumerable: prop !== 'toJSON' && prop !== 'validate',
396
+ };
397
+ },
398
+ },
399
+ );
400
+ }
@@ -0,0 +1,87 @@
1
+ // ============================================================
2
+ // ACTIVITY RESOLVER
3
+ // ============================================================
4
+ // RHF resolver for Activity field validation.
5
+ // Accepts either an Activity instance or a fields map (for
6
+ // metadata-driven forms). Validates using BaseField.validate()
7
+ // plus constraint validation (required, length, number precision).
8
+
9
+ import type { FieldValues } from 'react-hook-form';
10
+ import type { Activity } from '../../../workflow/Activity';
11
+ import type { BaseField } from '../../../bdo/fields/BaseField';
12
+ import type { ValidationResultType } from '../../../bdo/core/types';
13
+ import { validateConstraints } from '../useForm/createResolver';
14
+
15
+ /**
16
+ * Creates a React Hook Form resolver for Activity field validation.
17
+ *
18
+ * Validates only editable fields using BaseField.validate() + constraint checks.
19
+ * Readonly fields (readOnly: true) are skipped.
20
+ *
21
+ * @param activityOrFields - An Activity instance or a Record of field name to BaseField
22
+ * @returns RHF Resolver function
23
+ */
24
+ export function createActivityResolver<A extends Activity<any, any, any>>(
25
+ activityOrFields: A | Record<string, BaseField<unknown>>,
26
+ ) {
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ return async (values: FieldValues, _context: any, options: any) => {
29
+ const errors: Record<string, { type: string; message: string }> = {};
30
+
31
+ // Accept either an Activity instance or a raw fields map
32
+ const fields: Record<string, BaseField<unknown>> =
33
+ '_getFields' in activityOrFields
34
+ ? (activityOrFields as A)._getFields()
35
+ : activityOrFields;
36
+
37
+ // If validating specific fields (blur/change), only validate those
38
+ // If validating all (submit), options.names is undefined
39
+ const fieldsToValidate = options?.names ?? Object.keys(fields);
40
+
41
+ for (const fieldName of fieldsToValidate) {
42
+ const field = fields[fieldName];
43
+ if (!field) continue;
44
+
45
+ // Skip validation for readonly fields
46
+ if (field.readOnly) continue;
47
+
48
+ let value = values[fieldName];
49
+
50
+ // Coerce string values from HTML inputs to the expected type
51
+ if (typeof value === 'string' && field.meta.Type === 'Number') {
52
+ value = value === '' ? undefined : Number(value);
53
+ }
54
+
55
+ // 1. Type validation
56
+ const typeResult: ValidationResultType = (
57
+ field as BaseField<unknown>
58
+ ).validate(value);
59
+
60
+ if (!typeResult.valid && typeResult.errors.length > 0) {
61
+ errors[fieldName] = {
62
+ type: 'validate',
63
+ message: typeResult.errors[0] || `${fieldName} is invalid`,
64
+ };
65
+ continue;
66
+ }
67
+
68
+ // 2. Constraint validation (required, length, number precision)
69
+ const constraintResult = validateConstraints(
70
+ field as BaseField<unknown>,
71
+ value,
72
+ );
73
+ if (!constraintResult.valid && constraintResult.errors.length > 0) {
74
+ errors[fieldName] = {
75
+ type: 'constraint',
76
+ message: constraintResult.errors[0],
77
+ };
78
+ }
79
+ }
80
+
81
+ // Return format for RHF resolver
82
+ if (Object.keys(errors).length === 0) {
83
+ return { values, errors: {} };
84
+ }
85
+ return { values: {}, errors };
86
+ };
87
+ }
@@ -12,9 +12,9 @@ import type {
12
12
  UseFormTrigger,
13
13
  Control,
14
14
  FieldErrors,
15
- } from "react-hook-form";
15
+ } from 'react-hook-form';
16
16
 
17
- import type { Activity } from "../../Activity";
17
+ import type { Activity } from '../../../workflow/Activity';
18
18
 
19
19
  // Reuse shared types from useForm — identical interfaces, no duplication
20
20
  import type {
@@ -23,7 +23,7 @@ import type {
23
23
  FormRegisterType,
24
24
  EditableFormFieldAccessorType,
25
25
  ReadonlyFormFieldAccessorType,
26
- } from "../../../components/hooks/useForm/types";
26
+ } from '../useForm/types';
27
27
 
28
28
  // Re-export for consumers who import from this module
29
29
  export type {
@@ -75,7 +75,7 @@ export interface UseActivityFormOptions<A extends Activity<any, any, any>> {
75
75
  * Validation mode — controls when validation runs
76
76
  * @default "onBlur"
77
77
  */
78
- mode?: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all";
78
+ mode?: 'onBlur' | 'onChange' | 'onSubmit' | 'onTouched' | 'all';
79
79
 
80
80
  /**
81
81
  * Whether to enable activity.read() on mount
@@ -99,12 +99,15 @@ export interface UseActivityFormReturn<A extends Activity<any, any, any>> {
99
99
  activity: A;
100
100
 
101
101
  /** Smart register with auto-disable for readonly fields */
102
- register: FormRegisterType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>>;
102
+ register: FormRegisterType<
103
+ ExtractActivityEditable<A>,
104
+ ExtractActivityReadonly<A>
105
+ >;
103
106
 
104
- /** Handle form submission — calls activity.update() + activity.draftEnd() */
107
+ /** Handle form submission — calls activity.update() */
105
108
  handleSubmit: HandleSubmitType<AllActivityFields<A>>;
106
109
 
107
- /** Handle form completion — calls activity.complete() */
110
+ /** Handle form completion — calls activity.update() + activity.complete() */
108
111
  handleComplete: HandleSubmitType<AllActivityFields<A>>;
109
112
 
110
113
  /** Watch field values */
@@ -148,15 +151,25 @@ export interface UseActivityFormReturn<A extends Activity<any, any, any>> {
148
151
  // LOADING STATES
149
152
  // ============================================================
150
153
 
151
- /** True during activity.read() */
154
+ /** True during activity.read() or metadata fetch */
152
155
  isLoading: boolean;
153
156
 
157
+ /** True while BP metadata and context schemas are loading */
158
+ isMetadataLoading: boolean;
159
+
154
160
  /** Schema/data load error */
155
161
  loadError: Error | null;
156
162
 
157
163
  /** Any error active */
158
164
  hasError: boolean;
159
165
 
166
+ // ============================================================
167
+ // METADATA (optional — for consumers that need raw metadata)
168
+ // ============================================================
169
+
170
+ /** Raw BP metadata (BDOBlob) — available after metadata fetch completes */
171
+ bpMetadata: Record<string, unknown> | null;
172
+
160
173
  // ============================================================
161
174
  // OPERATIONS
162
175
  // ============================================================