@ram_28/kf-ai-sdk 2.0.6 → 2.0.9

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 (79) hide show
  1. package/dist/api/client.d.ts +21 -1
  2. package/dist/api/client.d.ts.map +1 -1
  3. package/dist/api/index.d.ts +1 -1
  4. package/dist/api/index.d.ts.map +1 -1
  5. package/dist/api.cjs +1 -1
  6. package/dist/api.mjs +2 -2
  7. package/dist/api.types.d.ts +1 -1
  8. package/dist/api.types.d.ts.map +1 -1
  9. package/dist/auth.cjs +1 -1
  10. package/dist/auth.mjs +1 -1
  11. package/dist/bdo/core/BaseBdo.d.ts +21 -1
  12. package/dist/bdo/core/BaseBdo.d.ts.map +1 -1
  13. package/dist/bdo/core/Item.d.ts +22 -3
  14. package/dist/bdo/core/Item.d.ts.map +1 -1
  15. package/dist/bdo/core/types.d.ts +28 -0
  16. package/dist/bdo/core/types.d.ts.map +1 -1
  17. package/dist/bdo/fields/FileField.d.ts +2 -2
  18. package/dist/bdo/fields/FileField.d.ts.map +1 -1
  19. package/dist/bdo/fields/ImageField.d.ts +18 -0
  20. package/dist/bdo/fields/ImageField.d.ts.map +1 -0
  21. package/dist/bdo/fields/attachment-constants.d.ts +9 -0
  22. package/dist/bdo/fields/attachment-constants.d.ts.map +1 -0
  23. package/dist/bdo/fields/index.d.ts +1 -0
  24. package/dist/bdo/fields/index.d.ts.map +1 -1
  25. package/dist/bdo/index.d.ts +2 -2
  26. package/dist/bdo/index.d.ts.map +1 -1
  27. package/dist/bdo.cjs +1 -1
  28. package/dist/bdo.d.ts +1 -1
  29. package/dist/bdo.d.ts.map +1 -1
  30. package/dist/bdo.mjs +381 -157
  31. package/dist/bdo.types.d.ts +2 -2
  32. package/dist/bdo.types.d.ts.map +1 -1
  33. package/dist/client-BnVxSHAm.cjs +1 -0
  34. package/dist/client-CMERmrC-.js +279 -0
  35. package/dist/form.cjs +1 -1
  36. package/dist/form.mjs +14 -14
  37. package/dist/{metadata-BJWukIqS.cjs → metadata-BfJtHz84.cjs} +1 -1
  38. package/dist/{metadata-CJuFxytC.js → metadata-CwAo6a8e.js} +1 -1
  39. package/dist/table.cjs +1 -1
  40. package/dist/table.mjs +1 -1
  41. package/dist/types/base-fields.d.ts +19 -3
  42. package/dist/types/base-fields.d.ts.map +1 -1
  43. package/dist/types/common.d.ts +40 -0
  44. package/dist/types/common.d.ts.map +1 -1
  45. package/dist/types/constants.d.ts +8 -0
  46. package/dist/types/constants.d.ts.map +1 -1
  47. package/dist/workflow/Activity.d.ts +11 -9
  48. package/dist/workflow/Activity.d.ts.map +1 -1
  49. package/dist/workflow/client.d.ts +12 -10
  50. package/dist/workflow/client.d.ts.map +1 -1
  51. package/dist/workflow/types.d.ts +12 -11
  52. package/dist/workflow/types.d.ts.map +1 -1
  53. package/dist/workflow.cjs +1 -1
  54. package/dist/workflow.mjs +201 -213
  55. package/docs/useForm.md +174 -0
  56. package/docs/workflow.md +38 -76
  57. package/package.json +1 -1
  58. package/sdk/api/client.ts +145 -0
  59. package/sdk/api/index.ts +4 -0
  60. package/sdk/api.types.ts +5 -0
  61. package/sdk/bdo/core/BaseBdo.ts +60 -0
  62. package/sdk/bdo/core/Item.ts +231 -3
  63. package/sdk/bdo/core/types.ts +63 -0
  64. package/sdk/bdo/fields/FileField.ts +14 -5
  65. package/sdk/bdo/fields/ImageField.ts +46 -0
  66. package/sdk/bdo/fields/attachment-constants.ts +72 -0
  67. package/sdk/bdo/fields/index.ts +1 -0
  68. package/sdk/bdo/index.ts +6 -0
  69. package/sdk/bdo.ts +1 -0
  70. package/sdk/bdo.types.ts +7 -0
  71. package/sdk/components/hooks/useForm/createResolver.ts +1 -1
  72. package/sdk/types/base-fields.ts +21 -3
  73. package/sdk/types/common.ts +45 -0
  74. package/sdk/types/constants.ts +8 -0
  75. package/sdk/workflow/Activity.ts +18 -26
  76. package/sdk/workflow/client.ts +25 -47
  77. package/sdk/workflow/types.ts +11 -12
  78. package/dist/client-BULEEaCP.js +0 -222
  79. package/dist/client-DtPpfJc1.cjs +0 -1
package/docs/useForm.md CHANGED
@@ -174,6 +174,63 @@ interface ReadonlyFormFieldAccessorType<T> {
174
174
  }
175
175
  ```
176
176
 
177
+ ### File & Image Value Types
178
+
179
+ ```typescript
180
+ // Metadata for an uploaded file or image
181
+ interface FileType {
182
+ _id: string;
183
+ _name: string;
184
+ FileName: string;
185
+ FileExtension: string;
186
+ Size: number;
187
+ ContentType: string;
188
+ }
189
+
190
+ type ImageFieldType = FileType | null; // Single image, nullable
191
+ type FileFieldType = FileType[]; // Array of files
192
+
193
+ // Request a thumbnail or preview variant from the backend
194
+ type AttachmentViewType = "thumbnail" | "preview";
195
+ ```
196
+
197
+ ### File & Image Accessor Types
198
+
199
+ Image and File fields extend the base accessor with attachment operations.
200
+ Editable accessors get `upload` and `deleteAttachment`; readonly accessors only get download.
201
+
202
+ ```typescript
203
+ // Editable Image — single file
204
+ interface EditableImageFieldAccessorType
205
+ extends EditableFormFieldAccessorType<ImageFieldType> {
206
+ upload(file: File): Promise<FileType>;
207
+ getDownloadUrl(viewType?: AttachmentViewType): Promise<FileDownloadResponseType>;
208
+ deleteAttachment(): Promise<void>;
209
+ }
210
+
211
+ // Readonly Image — download only
212
+ interface ReadonlyImageFieldAccessorType
213
+ extends ReadonlyFormFieldAccessorType<ImageFieldType> {
214
+ getDownloadUrl(viewType?: AttachmentViewType): Promise<FileDownloadResponseType>;
215
+ }
216
+
217
+ // Editable File — multi-file
218
+ interface EditableFileFieldAccessorType
219
+ extends EditableFormFieldAccessorType<FileFieldType> {
220
+ upload(files: File[]): Promise<FileType[]>;
221
+ getDownloadUrl(attachmentId: string, viewType?: AttachmentViewType): Promise<FileDownloadResponseType>;
222
+ getDownloadUrls(viewType?: AttachmentViewType): Promise<FileDownloadResponseType[]>;
223
+ deleteAttachment(attachmentId: string): Promise<void>;
224
+ }
225
+
226
+ // Readonly File — download only
227
+ interface ReadonlyFileFieldAccessorType
228
+ extends ReadonlyFormFieldAccessorType<FileFieldType> {
229
+ getDownloadUrl(attachmentId: string, viewType?: AttachmentViewType): Promise<FileDownloadResponseType>;
230
+ getDownloadUrls(viewType?: AttachmentViewType): Promise<FileDownloadResponseType[]>;
231
+ }
232
+ ```
233
+
177
234
  ---
178
235
 
179
236
  ## Basic Example
@@ -720,6 +777,123 @@ function ProductForm({ productId, onSuccess }: ProductFormProps) {
720
777
 
721
778
  ---
722
779
 
780
+ ## File & Image Attachments in Forms
781
+
782
+ Image and File fields get attachment methods on the `item` accessor automatically.
783
+ `upload()` updates local field state only — the form's `handleSubmit` persists everything to the backend.
784
+ `deleteAttachment()` is atomic — it removes the file from storage and the backend record immediately.
785
+
786
+ ```tsx
787
+ // Assuming SellerProduct BDO has:
788
+ // readonly ProductImage = new ImageField({ _id: "ProductImage", Name: "Product Image", Type: "Image" });
789
+ // readonly Attachments = new FileField({ _id: "Attachments", Name: "Attachments", Type: "File" });
790
+
791
+ function ProductFormWithAttachments({ productId }: { productId?: string }) {
792
+ const product = useMemo(() => new SellerProduct(), []);
793
+
794
+ const { register, handleSubmit, item, errors, isSubmitting, watch } = useForm({
795
+ bdo: product,
796
+ recordId: productId,
797
+ mode: ValidationMode.OnBlur,
798
+ });
799
+
800
+ // --- Image field (single file) ---
801
+
802
+ const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
803
+ const file = e.target.files?.[0];
804
+ if (!file) return;
805
+
806
+ // Uploads to storage, sets local value (FileType)
807
+ // Validates extension client-side before uploading
808
+ const metadata = await item.ProductImage.upload(file);
809
+ console.log("Uploaded image:", metadata.FileName);
810
+ };
811
+
812
+ const handleImageDelete = async () => {
813
+ // Atomic — deletes from storage + backend immediately
814
+ await item.ProductImage.deleteAttachment();
815
+ };
816
+
817
+ const handleImagePreview = async () => {
818
+ // Get a thumbnail URL (backend-generated variant)
819
+ const { DownloadUrl } = await item.ProductImage.getDownloadUrl("thumbnail");
820
+ window.open(DownloadUrl);
821
+ };
822
+
823
+ // --- File field (multi-file) ---
824
+
825
+ const handleFilesUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
826
+ const files = Array.from(e.target.files ?? []);
827
+ if (!files.length) return;
828
+
829
+ // Uploads all files in parallel, appends to existing array
830
+ const uploaded = await item.Attachments.upload(files);
831
+ console.log(`Uploaded ${uploaded.length} files`);
832
+ };
833
+
834
+ const handleFileDelete = async (attachmentId: string) => {
835
+ // Removes single file from storage + backend + local array
836
+ await item.Attachments.deleteAttachment(attachmentId);
837
+ };
838
+
839
+ const handleFileDownload = async (attachmentId: string) => {
840
+ const { DownloadUrl } = await item.Attachments.getDownloadUrl(attachmentId);
841
+ window.open(DownloadUrl);
842
+ };
843
+
844
+ // Current field values
845
+ const currentImage = item.ProductImage.get(); // FileType | null
846
+ const currentFiles = item.Attachments.get() ?? []; // FileType[]
847
+
848
+ return (
849
+ <form onSubmit={handleSubmit((data) => console.log("Saved:", data._id))}>
850
+ {/* ... other fields with register() ... */}
851
+
852
+ {/* Image field */}
853
+ <div className="field">
854
+ <label>{product.ProductImage.label}</label>
855
+ {currentImage ? (
856
+ <div>
857
+ <span>{currentImage.FileName}</span>
858
+ <button type="button" onClick={handleImagePreview}>Preview</button>
859
+ <button type="button" onClick={handleImageDelete}>Remove</button>
860
+ </div>
861
+ ) : (
862
+ <input type="file" accept="image/*" onChange={handleImageUpload} />
863
+ )}
864
+ </div>
865
+
866
+ {/* File field */}
867
+ <div className="field">
868
+ <label>{product.Attachments.label}</label>
869
+ <input type="file" multiple onChange={handleFilesUpload} />
870
+ <ul>
871
+ {currentFiles.map((file) => (
872
+ <li key={file._id}>
873
+ {file.FileName} ({file.Size} bytes)
874
+ <button type="button" onClick={() => handleFileDownload(file._id)}>Download</button>
875
+ <button type="button" onClick={() => handleFileDelete(file._id)}>Delete</button>
876
+ </li>
877
+ ))}
878
+ </ul>
879
+ </div>
880
+
881
+ <button type="submit" disabled={isSubmitting}>
882
+ {isSubmitting ? "Saving..." : "Save"}
883
+ </button>
884
+ </form>
885
+ );
886
+ }
887
+ ```
888
+
889
+ **Key points:**
890
+ - `upload()` validates file extensions client-side (throws on unsupported types)
891
+ - Image upload replaces the current value; File upload appends to the array
892
+ - `getDownloadUrl("thumbnail")` / `getDownloadUrl("preview")` request backend-generated variants
893
+ - Readonly Image/File fields only have `getDownloadUrl` — no `upload` or `deleteAttachment`
894
+
895
+ ---
896
+
723
897
  ## Constants Reference
724
898
 
725
899
  ```typescript
package/docs/workflow.md CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  StringField,
28
28
  NumberField,
29
29
  BooleanField,
30
+ DateField,
30
31
  DateTimeField,
31
32
  SelectField,
32
33
  ReferenceField,
@@ -52,8 +53,9 @@ import type {
52
53
 
53
54
  ```typescript
54
55
  interface WorkflowStartResponseType {
55
- activityId: string; // First activity ID in the workflow
56
- activityInstanceId: string; // Instance ID for the started activity
56
+ BPInstanceId: string; // Business process instance ID
57
+ ActivityId: string; // First activity ID in the workflow
58
+ _id: string; // Activity instance ID
57
59
  }
58
60
  ```
59
61
 
@@ -170,17 +172,13 @@ export class EmployeeInputActivity extends Activity<
170
172
  activityId: "EMPLOYEE_INPUT",
171
173
  };
172
174
 
173
- readonly StartDate = new DateTimeField({ id: "StartDate", label: "Start Date" });
174
- readonly EndDate = new DateTimeField({ id: "EndDate", label: "End Date" });
175
+ readonly StartDate = new DateField({ "_id": "StartDate", "Name": "Start Date", "Type": "Date" });
176
+ readonly EndDate = new DateField({ "_id": "EndDate", "Name": "End Date", "Type": "Date" });
175
177
  readonly LeaveType = new SelectField<"PTO" | "Sick" | "Parental">({
176
- id: "LeaveType", label: "Leave Type",
177
- options: [
178
- { value: "PTO", label: "PTO" },
179
- { value: "Sick", label: "Sick" },
180
- { value: "Parental", label: "Parental" },
181
- ],
178
+ "_id": "LeaveType", "Name": "Leave Type", "Type": "String",
179
+ "Constraint": { "Enum": ["PTO", "Sick", "Parental"] },
182
180
  });
183
- readonly LeaveDays = new NumberField({ id: "LeaveDays", label: "Leave Days", editable: false });
181
+ readonly LeaveDays = new NumberField({ "_id": "LeaveDays", "Name": "Leave Days", "Type": "Number", "ReadOnly": true });
184
182
  }
185
183
 
186
184
  export type ManagerApprovalEntityType = {
@@ -198,14 +196,15 @@ export class ManagerApprovalActivity extends Activity<
198
196
  activityId: "MANAGER_APPROVAL",
199
197
  };
200
198
 
201
- readonly ManagerApproved = new BooleanField({ id: "ManagerApproved", label: "Manager Approved" });
202
- readonly ManagerReason = new StringField({ id: "ManagerReason", label: "Manager's Reason" });
199
+ readonly ManagerApproved = new BooleanField({ "_id": "ManagerApproved", "Name": "Manager Approved", "Type": "Boolean" });
200
+ readonly ManagerReason = new StringField({ "_id": "ManagerReason", "Name": "Manager's Reason", "Type": "String" });
203
201
  }
204
202
 
205
203
  // --- Workflow class ---
206
204
 
207
205
  export class SimpleLeaveProcess {
208
206
  async start(): Promise<WorkflowStartResponseType>;
207
+ async progress(instance_id: string): Promise<ActivityProgressType[]>;
209
208
 
210
209
  employeeInputActivity(): EmployeeInputActivity;
211
210
  managerApprovalActivity(): ManagerApprovalActivity;
@@ -222,23 +221,24 @@ Start a new workflow instance.
222
221
 
223
222
  ```typescript
224
223
  const wf = new SimpleLeaveProcess();
225
- const { activityId, activityInstanceId } = await wf.start();
224
+ const { BPInstanceId, ActivityId, _id } = await wf.start();
226
225
  ```
227
226
 
228
- ### progress()
227
+ ### progress(instance_id)
229
228
 
230
- Get global progress across the entire business process. Returns a list of progress entries for each stage/activity.
229
+ Get progress for a specific process instance. Returns a list of progress entries for each stage/activity.
231
230
 
232
231
  ```typescript
233
232
  const wf = new SimpleLeaveProcess();
234
- const progressList = await wf.progress();
233
+ const { BPInstanceId } = await wf.start();
234
+ const progressList = await wf.progress(BPInstanceId);
235
235
  // progressList: ActivityProgressType[]
236
236
  for (const entry of progressList) {
237
237
  console.log(entry._name, entry.Status, entry.CompletedAt);
238
238
  }
239
239
  ```
240
240
 
241
- **Endpoint:** `GET /api/app/process/{bp_id}/progress`
241
+ **Endpoint:** `GET /api/app/process/{bp_id}/{instance_id}/progress`
242
242
 
243
243
  ---
244
244
 
@@ -250,54 +250,44 @@ Each Activity class provides methods to query and access activity instances. Lis
250
250
  const activity = wf.employeeInputActivity();
251
251
  ```
252
252
 
253
- ### getInProgressList(options?)
253
+ ### getInProgressList()
254
254
 
255
- List in-progress activity instances with optional filtering and pagination.
255
+ List in-progress activity instances. Filtering and pagination are handled server-side automatically.
256
256
 
257
257
  ```typescript
258
- const result = await activity.getInProgressList({
259
- Page: 1,
260
- PageSize: 20,
261
- });
258
+ const result = await activity.getInProgressList();
262
259
 
263
260
  for (const item of result.Data) {
264
261
  console.log(item._id, item.Status, item.StartDate);
265
262
  }
266
263
  ```
267
264
 
268
- ### getCompletedList(options?)
265
+ ### getCompletedList()
269
266
 
270
- List completed activity instances with optional filtering and pagination.
267
+ List completed activity instances. Filtering and pagination are handled server-side automatically.
271
268
 
272
269
  ```typescript
273
- const result = await activity.getCompletedList({
274
- Page: 1,
275
- PageSize: 20,
276
- });
270
+ const result = await activity.getCompletedList();
277
271
 
278
272
  for (const item of result.Data) {
279
273
  console.log(item._id, item.CompletedAt, item.StartDate);
280
274
  }
281
275
  ```
282
276
 
283
- ### inProgressMetrics(options)
277
+ ### inProgressMetrics()
284
278
 
285
279
  Get aggregated metrics for in-progress activity instances.
286
280
 
287
281
  ```typescript
288
- const metrics = await activity.inProgressMetrics({
289
- Metric: [{ Field: "Status", Function: "Count" }],
290
- });
282
+ const metrics = await activity.inProgressMetrics();
291
283
  ```
292
284
 
293
- ### completedMetrics(options)
285
+ ### completedMetrics()
294
286
 
295
287
  Get aggregated metrics for completed activity instances.
296
288
 
297
289
  ```typescript
298
- const metrics = await activity.completedMetrics({
299
- Metric: [{ Field: "Status", Function: "Count" }],
300
- });
290
+ const metrics = await activity.completedMetrics();
301
291
  ```
302
292
 
303
293
  ### getInstance(instanceId)
@@ -311,7 +301,7 @@ const instance = await activity.getInstance("inst_abc123");
311
301
  instance._id; // "inst_abc123"
312
302
  instance.StartDate.get(); // read value (typed)
313
303
  instance.StartDate.set("2026-03-01"); // set value (editable fields only)
314
- instance.StartDate.meta; // { id: "StartDate", label: "Start Date", ... }
304
+ instance.StartDate.meta; // { _id: "StartDate", Name: "Start Date", Type: "Date" }
315
305
  instance.LeaveDays.get(); // read computed/readonly field
316
306
  // instance.LeaveDays.set(...) // NOT available — readonly, no set()
317
307
 
@@ -401,10 +391,10 @@ import { SimpleLeaveProcess } from "@/bdo/workflows/SimpleLeaveProcess";
401
391
  import type { WorkflowStartResponseType } from "@ram_28/kf-ai-sdk/workflow";
402
392
 
403
393
  const wf = new SimpleLeaveProcess();
404
- const { activityId, activityInstanceId }: WorkflowStartResponseType = await wf.start();
394
+ const { BPInstanceId, ActivityId, _id }: WorkflowStartResponseType = await wf.start();
405
395
 
406
- // Check global progress at any time
407
- const progress = await wf.progress();
396
+ // Check progress at any time
397
+ const progress = await wf.progress(BPInstanceId);
408
398
  ```
409
399
 
410
400
  ### Step 2 — React component with useActivityForm
@@ -532,7 +522,7 @@ function LeaveRequestPage() {
532
522
  const startLeaveRequest = async () => {
533
523
  const wf = new SimpleLeaveProcess();
534
524
  const result: WorkflowStartResponseType = await wf.start();
535
- setInstanceId(result.activityInstanceId);
525
+ setInstanceId(result._id);
536
526
  };
537
527
 
538
528
  if (!instanceId) {
@@ -560,10 +550,7 @@ import { SimpleLeaveProcess } from "@/bdo/workflows/SimpleLeaveProcess";
560
550
  const wf = new SimpleLeaveProcess();
561
551
  const activity = wf.managerApprovalActivity();
562
552
 
563
- const result = await activity.getInProgressList({
564
- Page: 1,
565
- PageSize: 20,
566
- });
553
+ const result = await activity.getInProgressList();
567
554
 
568
555
  for (const item of result.Data) {
569
556
  console.log(item._id, item.Status, item.AssignedTo.username);
@@ -685,7 +672,7 @@ const progress = await instance.progress();
685
672
 
686
673
  ## Filtering Reference
687
674
 
688
- Status filtering is built into the method names (`getInProgressList` / `getCompletedList`), so you no longer need a Status filter condition.
675
+ Status filtering is built into the method names (`getInProgressList` / `getCompletedList`). All filtering (by current user, activity status, and activity ID) and pagination are handled server-side automatically — no client-side options are needed.
689
676
 
690
677
  ### In-progress items
691
678
 
@@ -699,37 +686,12 @@ const result = await activity.getInProgressList();
699
686
  const result = await activity.getCompletedList();
700
687
  ```
701
688
 
702
- ### Assigned to a specific user (in-progress)
703
-
704
- ```typescript
705
- const result = await activity.getInProgressList({
706
- Filter: {
707
- Operator: "And",
708
- Condition: [
709
- { LHSField: "AssignedTo._id", Operator: "EQ", RHSValue: userId, RHSType: "Constant" },
710
- ],
711
- },
712
- });
713
- ```
714
-
715
- ### Assigned to a specific user (completed)
716
-
717
- ```typescript
718
- const result = await activity.getCompletedList({
719
- Filter: {
720
- Operator: "And",
721
- Condition: [
722
- { LHSField: "AssignedTo._id", Operator: "EQ", RHSValue: userId, RHSType: "Constant" },
723
- ],
724
- },
725
- });
726
- ```
727
-
728
- ### Global process progress
689
+ ### Process progress
729
690
 
730
691
  ```typescript
731
692
  const wf = new SimpleLeaveProcess();
732
- const progressList = await wf.progress();
693
+ const { BPInstanceId } = await wf.start();
694
+ const progressList = await wf.progress(BPInstanceId);
733
695
  // Returns ActivityProgressType[] — one entry per activity in the process
734
696
  ```
735
697
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ram_28/kf-ai-sdk",
3
- "version": "2.0.6",
3
+ "version": "2.0.9",
4
4
  "description": "Type-safe, AI-driven SDK for building modern web applications with role-based access control",
5
5
  "author": "Ramprasad",
6
6
  "license": "MIT",
package/sdk/api/client.ts CHANGED
@@ -16,6 +16,10 @@ import type {
16
16
  DraftResponseType,
17
17
  FieldsResponseType,
18
18
  FetchFieldOptionType,
19
+ FileUploadRequestType,
20
+ FileUploadResponseType,
21
+ FileDownloadResponseType,
22
+ AttachmentViewType,
19
23
  } from "../types/common";
20
24
 
21
25
  /**
@@ -115,6 +119,51 @@ export interface ResourceClientType<T = any> {
115
119
  instanceId: string,
116
120
  fieldId: string,
117
121
  ): Promise<TResult[]>;
122
+
123
+ // ============================================================
124
+ // ATTACHMENT OPERATIONS
125
+ // ============================================================
126
+
127
+ /**
128
+ * Get signed upload URLs for file attachments
129
+ * POST /{bo_id}/{instance_id}/field/{field_id}/attachment/upload
130
+ */
131
+ getUploadUrl(
132
+ instanceId: string,
133
+ fieldId: string,
134
+ files: FileUploadRequestType[],
135
+ ): Promise<FileUploadResponseType[]>;
136
+
137
+ /**
138
+ * Get signed download URL for a single attachment
139
+ * GET /{bo_id}/{instance_id}/field/{field_id}/attachment/{attachment_id}/read
140
+ */
141
+ getDownloadUrl(
142
+ instanceId: string,
143
+ fieldId: string,
144
+ attachmentId: string,
145
+ viewType?: AttachmentViewType,
146
+ ): Promise<FileDownloadResponseType>;
147
+
148
+ /**
149
+ * Get signed download URLs for all attachments on a field
150
+ * GET /{bo_id}/{instance_id}/field/{field_id}/attachment/read
151
+ */
152
+ getDownloadUrls(
153
+ instanceId: string,
154
+ fieldId: string,
155
+ viewType?: AttachmentViewType,
156
+ ): Promise<FileDownloadResponseType[]>;
157
+
158
+ /**
159
+ * Delete an attachment
160
+ * DELETE /{bo_id}/{instance_id}/field/{field_id}/attachment/{attachment_id}/delete
161
+ */
162
+ deleteAttachment(
163
+ instanceId: string,
164
+ fieldId: string,
165
+ attachmentId: string,
166
+ ): Promise<void>;
118
167
  }
119
168
 
120
169
  /**
@@ -455,6 +504,102 @@ export function createResourceClient<T = any>(
455
504
  const responseData: { Data: TResult[] } = await response.json();
456
505
  return responseData.Data;
457
506
  },
507
+
508
+ // ============================================================
509
+ // ATTACHMENT OPERATIONS
510
+ // ============================================================
511
+
512
+ async getUploadUrl(
513
+ instanceId: string,
514
+ fieldId: string,
515
+ files: FileUploadRequestType[],
516
+ ): Promise<FileUploadResponseType[]> {
517
+ const response = await fetch(
518
+ `${baseUrl}${basePath}/${instanceId}/field/${fieldId}/attachment/upload`,
519
+ {
520
+ method: "POST",
521
+ headers: getDefaultHeaders(),
522
+ body: JSON.stringify(files),
523
+ },
524
+ );
525
+
526
+ if (!response.ok) {
527
+ throw new Error(
528
+ `Failed to get upload URL for ${basePath}/${fieldId}: ${response.statusText}`,
529
+ );
530
+ }
531
+
532
+ const responseData: { Data: FileUploadResponseType[] } =
533
+ await response.json();
534
+ return responseData.Data;
535
+ },
536
+
537
+ async getDownloadUrl(
538
+ instanceId: string,
539
+ fieldId: string,
540
+ attachmentId: string,
541
+ viewType?: AttachmentViewType,
542
+ ): Promise<FileDownloadResponseType> {
543
+ let url = `${baseUrl}${basePath}/${instanceId}/field/${fieldId}/attachment/${attachmentId}/read`;
544
+ if (viewType) url += `?view_type=${viewType}`;
545
+ const response = await fetch(url, {
546
+ method: "GET",
547
+ headers: getDefaultHeaders(),
548
+ });
549
+
550
+ if (!response.ok) {
551
+ throw new Error(
552
+ `Failed to get download URL for ${basePath}/${fieldId}/${attachmentId}: ${response.statusText}`,
553
+ );
554
+ }
555
+
556
+ const responseData: { Data: FileDownloadResponseType } =
557
+ await response.json();
558
+ return responseData.Data;
559
+ },
560
+
561
+ async getDownloadUrls(
562
+ instanceId: string,
563
+ fieldId: string,
564
+ viewType?: AttachmentViewType,
565
+ ): Promise<FileDownloadResponseType[]> {
566
+ let url = `${baseUrl}${basePath}/${instanceId}/field/${fieldId}/attachment/read`;
567
+ if (viewType) url += `?view_type=${viewType}`;
568
+ const response = await fetch(url, {
569
+ method: "GET",
570
+ headers: getDefaultHeaders(),
571
+ });
572
+
573
+ if (!response.ok) {
574
+ throw new Error(
575
+ `Failed to get download URLs for ${basePath}/${fieldId}: ${response.statusText}`,
576
+ );
577
+ }
578
+
579
+ const responseData: { Data: FileDownloadResponseType[] } =
580
+ await response.json();
581
+ return responseData.Data;
582
+ },
583
+
584
+ async deleteAttachment(
585
+ instanceId: string,
586
+ fieldId: string,
587
+ attachmentId: string,
588
+ ): Promise<void> {
589
+ const response = await fetch(
590
+ `${baseUrl}${basePath}/${instanceId}/field/${fieldId}/attachment/${attachmentId}/delete`,
591
+ {
592
+ method: "DELETE",
593
+ headers: getDefaultHeaders(),
594
+ },
595
+ );
596
+
597
+ if (!response.ok) {
598
+ throw new Error(
599
+ `Failed to delete attachment ${attachmentId} for ${basePath}/${fieldId}: ${response.statusText}`,
600
+ );
601
+ }
602
+ },
458
603
  };
459
604
  }
460
605
 
package/sdk/api/index.ts CHANGED
@@ -58,6 +58,10 @@ export type {
58
58
  DraftResponseType,
59
59
  // Fields types
60
60
  FieldsResponseType,
61
+ // Attachment types
62
+ FileUploadRequestType,
63
+ FileUploadResponseType,
64
+ FileDownloadResponseType,
61
65
  } from "../types/common";
62
66
 
63
67
  // Re-export date types from base-fields
package/sdk/api.types.ts CHANGED
@@ -54,4 +54,9 @@ export type {
54
54
  FieldsResponseType,
55
55
  FetchFieldOptionType,
56
56
  FetchFieldResponseType,
57
+
58
+ // Attachment types
59
+ FileUploadRequestType,
60
+ FileUploadResponseType,
61
+ FileDownloadResponseType,
57
62
  } from './types/common';