@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.
- package/dist/api/client.d.ts +21 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +2 -2
- package/dist/api.types.d.ts +1 -1
- package/dist/api.types.d.ts.map +1 -1
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/bdo/core/BaseBdo.d.ts +21 -1
- package/dist/bdo/core/BaseBdo.d.ts.map +1 -1
- package/dist/bdo/core/Item.d.ts +22 -3
- package/dist/bdo/core/Item.d.ts.map +1 -1
- package/dist/bdo/core/types.d.ts +28 -0
- package/dist/bdo/core/types.d.ts.map +1 -1
- package/dist/bdo/fields/FileField.d.ts +2 -2
- package/dist/bdo/fields/FileField.d.ts.map +1 -1
- package/dist/bdo/fields/ImageField.d.ts +18 -0
- package/dist/bdo/fields/ImageField.d.ts.map +1 -0
- package/dist/bdo/fields/attachment-constants.d.ts +9 -0
- package/dist/bdo/fields/attachment-constants.d.ts.map +1 -0
- package/dist/bdo/fields/index.d.ts +1 -0
- package/dist/bdo/fields/index.d.ts.map +1 -1
- package/dist/bdo/index.d.ts +2 -2
- package/dist/bdo/index.d.ts.map +1 -1
- package/dist/bdo.cjs +1 -1
- package/dist/bdo.d.ts +1 -1
- package/dist/bdo.d.ts.map +1 -1
- package/dist/bdo.mjs +381 -157
- package/dist/bdo.types.d.ts +2 -2
- package/dist/bdo.types.d.ts.map +1 -1
- package/dist/client-BnVxSHAm.cjs +1 -0
- package/dist/client-CMERmrC-.js +279 -0
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +14 -14
- package/dist/{metadata-BJWukIqS.cjs → metadata-BfJtHz84.cjs} +1 -1
- package/dist/{metadata-CJuFxytC.js → metadata-CwAo6a8e.js} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +1 -1
- package/dist/types/base-fields.d.ts +19 -3
- package/dist/types/base-fields.d.ts.map +1 -1
- package/dist/types/common.d.ts +40 -0
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/constants.d.ts +8 -0
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/workflow/Activity.d.ts +11 -9
- package/dist/workflow/Activity.d.ts.map +1 -1
- package/dist/workflow/client.d.ts +12 -10
- package/dist/workflow/client.d.ts.map +1 -1
- package/dist/workflow/types.d.ts +12 -11
- package/dist/workflow/types.d.ts.map +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.mjs +201 -213
- package/docs/useForm.md +174 -0
- package/docs/workflow.md +38 -76
- package/package.json +1 -1
- package/sdk/api/client.ts +145 -0
- package/sdk/api/index.ts +4 -0
- package/sdk/api.types.ts +5 -0
- package/sdk/bdo/core/BaseBdo.ts +60 -0
- package/sdk/bdo/core/Item.ts +231 -3
- package/sdk/bdo/core/types.ts +63 -0
- package/sdk/bdo/fields/FileField.ts +14 -5
- package/sdk/bdo/fields/ImageField.ts +46 -0
- package/sdk/bdo/fields/attachment-constants.ts +72 -0
- package/sdk/bdo/fields/index.ts +1 -0
- package/sdk/bdo/index.ts +6 -0
- package/sdk/bdo.ts +1 -0
- package/sdk/bdo.types.ts +7 -0
- package/sdk/components/hooks/useForm/createResolver.ts +1 -1
- package/sdk/types/base-fields.ts +21 -3
- package/sdk/types/common.ts +45 -0
- package/sdk/types/constants.ts +8 -0
- package/sdk/workflow/Activity.ts +18 -26
- package/sdk/workflow/client.ts +25 -47
- package/sdk/workflow/types.ts +11 -12
- package/dist/client-BULEEaCP.js +0 -222
- package/dist/client-DtPpfJc1.cjs +0 -1
package/sdk/bdo/core/BaseBdo.ts
CHANGED
|
@@ -12,6 +12,10 @@ import type {
|
|
|
12
12
|
PivotOptionsType,
|
|
13
13
|
PivotResponseType,
|
|
14
14
|
DraftResponseType,
|
|
15
|
+
FileUploadRequestType,
|
|
16
|
+
FileUploadResponseType,
|
|
17
|
+
FileDownloadResponseType,
|
|
18
|
+
AttachmentViewType,
|
|
15
19
|
} from "../../types/common";
|
|
16
20
|
import type { SystemFields } from "../../types/base-fields";
|
|
17
21
|
import { api } from "../../api/client";
|
|
@@ -62,6 +66,13 @@ export abstract class BaseBdo<
|
|
|
62
66
|
// FIELD DEFINITIONS (auto-discovered)
|
|
63
67
|
// ============================================================
|
|
64
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Get the Business Object ID for API calls
|
|
71
|
+
*/
|
|
72
|
+
getBoId(): string {
|
|
73
|
+
return this.meta._id;
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
/**
|
|
66
77
|
* Whether fields have been bound to this BDO
|
|
67
78
|
*/
|
|
@@ -299,4 +310,53 @@ export abstract class BaseBdo<
|
|
|
299
310
|
): Promise<PivotResponseType> {
|
|
300
311
|
return api<TEntity>(this.meta._id).pivot(options);
|
|
301
312
|
}
|
|
313
|
+
|
|
314
|
+
// ============================================================
|
|
315
|
+
// ATTACHMENT OPERATIONS
|
|
316
|
+
// ============================================================
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get signed upload URLs for file/image attachments
|
|
320
|
+
*/
|
|
321
|
+
protected async getUploadUrl(
|
|
322
|
+
instanceId: string,
|
|
323
|
+
fieldId: string,
|
|
324
|
+
files: FileUploadRequestType[],
|
|
325
|
+
): Promise<FileUploadResponseType[]> {
|
|
326
|
+
return api<TEntity>(this.meta._id).getUploadUrl(instanceId, fieldId, files);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get signed download URL for a single attachment
|
|
331
|
+
*/
|
|
332
|
+
protected async getDownloadUrl(
|
|
333
|
+
instanceId: string,
|
|
334
|
+
fieldId: string,
|
|
335
|
+
attachmentId: string,
|
|
336
|
+
viewType?: AttachmentViewType,
|
|
337
|
+
): Promise<FileDownloadResponseType> {
|
|
338
|
+
return api<TEntity>(this.meta._id).getDownloadUrl(instanceId, fieldId, attachmentId, viewType);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get signed download URLs for all attachments on a field
|
|
343
|
+
*/
|
|
344
|
+
protected async getDownloadUrls(
|
|
345
|
+
instanceId: string,
|
|
346
|
+
fieldId: string,
|
|
347
|
+
viewType?: AttachmentViewType,
|
|
348
|
+
): Promise<FileDownloadResponseType[]> {
|
|
349
|
+
return api<TEntity>(this.meta._id).getDownloadUrls(instanceId, fieldId, viewType);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Delete an attachment
|
|
354
|
+
*/
|
|
355
|
+
protected async deleteAttachment(
|
|
356
|
+
instanceId: string,
|
|
357
|
+
fieldId: string,
|
|
358
|
+
attachmentId: string,
|
|
359
|
+
): Promise<void> {
|
|
360
|
+
return api<TEntity>(this.meta._id).deleteAttachment(instanceId, fieldId, attachmentId);
|
|
361
|
+
}
|
|
302
362
|
}
|
package/sdk/bdo/core/Item.ts
CHANGED
|
@@ -9,8 +9,27 @@ import type {
|
|
|
9
9
|
EditableFieldAccessorType,
|
|
10
10
|
ReadonlyFieldAccessorType,
|
|
11
11
|
FieldAccessorType,
|
|
12
|
+
EditableImageFieldAccessorType,
|
|
13
|
+
ReadonlyImageFieldAccessorType,
|
|
14
|
+
EditableFileFieldAccessorType,
|
|
15
|
+
ReadonlyFileFieldAccessorType,
|
|
12
16
|
} from "./types";
|
|
13
17
|
import type { BaseField } from "../fields/BaseField";
|
|
18
|
+
import type {
|
|
19
|
+
FileType,
|
|
20
|
+
ImageFieldType,
|
|
21
|
+
FileFieldType,
|
|
22
|
+
} from "../../types/base-fields";
|
|
23
|
+
import type {
|
|
24
|
+
FileUploadRequestType,
|
|
25
|
+
FileDownloadResponseType,
|
|
26
|
+
AttachmentViewType,
|
|
27
|
+
} from "../../types/common";
|
|
28
|
+
import {
|
|
29
|
+
validateFileExtension,
|
|
30
|
+
extractFileExtension,
|
|
31
|
+
} from "../fields/attachment-constants";
|
|
32
|
+
import { api } from "../../api/client";
|
|
14
33
|
|
|
15
34
|
/**
|
|
16
35
|
* Interface for BDO that Item needs
|
|
@@ -23,23 +42,44 @@ interface BdoLike {
|
|
|
23
42
|
value: unknown,
|
|
24
43
|
allValues: Record<string, unknown>
|
|
25
44
|
): ValidationResultType;
|
|
45
|
+
getBoId(): string;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
// Re-export accessor types for convenience
|
|
29
49
|
export type { EditableFieldAccessorType, ReadonlyFieldAccessorType, FieldAccessorType, BaseFieldMetaType };
|
|
30
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Resolve editable accessor type — specialized for File/Image fields
|
|
53
|
+
*/
|
|
54
|
+
type ResolveEditableAccessor<T> =
|
|
55
|
+
[T] extends [ImageFieldType]
|
|
56
|
+
? EditableImageFieldAccessorType
|
|
57
|
+
: [T] extends [FileFieldType]
|
|
58
|
+
? EditableFileFieldAccessorType
|
|
59
|
+
: EditableFieldAccessorType<T>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve readonly accessor type — specialized for File/Image fields
|
|
63
|
+
*/
|
|
64
|
+
type ResolveReadonlyAccessor<T> =
|
|
65
|
+
[T] extends [ImageFieldType]
|
|
66
|
+
? ReadonlyImageFieldAccessorType
|
|
67
|
+
: [T] extends [FileFieldType]
|
|
68
|
+
? ReadonlyFileFieldAccessorType
|
|
69
|
+
: ReadonlyFieldAccessorType<T>;
|
|
70
|
+
|
|
31
71
|
/**
|
|
32
72
|
* Create editable accessor type for each field in TEditable
|
|
33
73
|
*/
|
|
34
74
|
type EditableAccessors<T> = {
|
|
35
|
-
[K in keyof T as K extends "_id" ? never : K]:
|
|
75
|
+
[K in keyof T as K extends "_id" ? never : K]: ResolveEditableAccessor<T[K]>;
|
|
36
76
|
};
|
|
37
77
|
|
|
38
78
|
/**
|
|
39
79
|
* Create readonly accessor type for each field in TReadonly
|
|
40
80
|
*/
|
|
41
81
|
type ReadonlyAccessors<T> = {
|
|
42
|
-
[K in keyof T as K extends "_id" ? never : K]:
|
|
82
|
+
[K in keyof T as K extends "_id" ? never : K]: ResolveReadonlyAccessor<T[K]>;
|
|
43
83
|
};
|
|
44
84
|
|
|
45
85
|
/**
|
|
@@ -90,7 +130,8 @@ export class Item<T extends Record<string, unknown>> {
|
|
|
90
130
|
prop === "_bdo" ||
|
|
91
131
|
prop === "_data" ||
|
|
92
132
|
prop === "_accessorCache" ||
|
|
93
|
-
prop === "_getAccessor"
|
|
133
|
+
prop === "_getAccessor" ||
|
|
134
|
+
prop === "_requireInstanceId"
|
|
94
135
|
) {
|
|
95
136
|
return Reflect.get(target, prop, receiver);
|
|
96
137
|
}
|
|
@@ -155,6 +196,20 @@ export class Item<T extends Record<string, unknown>> {
|
|
|
155
196
|
}) as Item<T>;
|
|
156
197
|
}
|
|
157
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Require instanceId or throw.
|
|
201
|
+
* TODO: Support create flow via draftInteraction to get temp _id
|
|
202
|
+
*/
|
|
203
|
+
private _requireInstanceId(): string {
|
|
204
|
+
const id = this._data._id as string | undefined;
|
|
205
|
+
if (!id) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"Cannot perform attachment operation: item has no _id. Save the item first.",
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return id;
|
|
211
|
+
}
|
|
212
|
+
|
|
158
213
|
/**
|
|
159
214
|
* Get or create a field accessor for the given field.
|
|
160
215
|
* Editable fields get set(), readonly fields do not.
|
|
@@ -234,6 +289,179 @@ export class Item<T extends Record<string, unknown>> {
|
|
|
234
289
|
};
|
|
235
290
|
}
|
|
236
291
|
|
|
292
|
+
// Enrich File/Image field accessors with attachment methods
|
|
293
|
+
if (meta.Type === "Image" || meta.Type === "File") {
|
|
294
|
+
const boId = this._bdo.getBoId();
|
|
295
|
+
const acc = accessor as unknown as Record<string, unknown>;
|
|
296
|
+
|
|
297
|
+
if (meta.Type === "Image") {
|
|
298
|
+
// Image field — single file
|
|
299
|
+
|
|
300
|
+
// getDownloadUrl — always available (editable + readonly)
|
|
301
|
+
acc.getDownloadUrl = async (
|
|
302
|
+
viewType?: AttachmentViewType,
|
|
303
|
+
): Promise<FileDownloadResponseType> => {
|
|
304
|
+
const instanceId = this._requireInstanceId();
|
|
305
|
+
const value = this._data[fieldId as keyof T] as
|
|
306
|
+
| FileType
|
|
307
|
+
| null
|
|
308
|
+
| undefined;
|
|
309
|
+
if (!value?._id) {
|
|
310
|
+
throw new Error(`${fieldId} has no image to download`);
|
|
311
|
+
}
|
|
312
|
+
return api(boId).getDownloadUrl(
|
|
313
|
+
instanceId,
|
|
314
|
+
fieldId,
|
|
315
|
+
value._id,
|
|
316
|
+
viewType,
|
|
317
|
+
);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// upload + deleteAttachment — editable only
|
|
321
|
+
if (!isReadOnly) {
|
|
322
|
+
/**
|
|
323
|
+
* Upload to storage and update local field value.
|
|
324
|
+
* Does NOT persist to backend — call save()/update() after uploading.
|
|
325
|
+
*/
|
|
326
|
+
acc.upload = async (file: File): Promise<FileType> => {
|
|
327
|
+
validateFileExtension(file.name, "Image");
|
|
328
|
+
const instanceId = this._requireInstanceId();
|
|
329
|
+
const request: FileUploadRequestType = {
|
|
330
|
+
FileName: file.name,
|
|
331
|
+
Size: file.size,
|
|
332
|
+
FileExtension: extractFileExtension(file.name),
|
|
333
|
+
};
|
|
334
|
+
const [uploadInfo] = await api(boId).getUploadUrl(
|
|
335
|
+
instanceId,
|
|
336
|
+
fieldId,
|
|
337
|
+
[request],
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
await fetch(uploadInfo.UploadUrl.URL, {
|
|
341
|
+
method: "PUT",
|
|
342
|
+
headers: { "Content-Type": uploadInfo.ContentType },
|
|
343
|
+
body: file,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const metadata: FileType = {
|
|
347
|
+
_id: uploadInfo._id,
|
|
348
|
+
_name: uploadInfo._name,
|
|
349
|
+
FileName: uploadInfo.FileName,
|
|
350
|
+
FileExtension: uploadInfo.FileExtension,
|
|
351
|
+
Size: uploadInfo.Size,
|
|
352
|
+
ContentType: uploadInfo.ContentType,
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
this._data[fieldId as keyof T] = metadata as T[keyof T];
|
|
356
|
+
return metadata;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
acc.deleteAttachment = async (): Promise<void> => {
|
|
360
|
+
const instanceId = this._requireInstanceId();
|
|
361
|
+
const value = this._data[fieldId as keyof T] as
|
|
362
|
+
| FileType
|
|
363
|
+
| null
|
|
364
|
+
| undefined;
|
|
365
|
+
if (!value?._id) {
|
|
366
|
+
throw new Error(`${fieldId} has no image to delete`);
|
|
367
|
+
}
|
|
368
|
+
await api(boId).deleteAttachment(instanceId, fieldId, value._id);
|
|
369
|
+
this._data[fieldId as keyof T] = null as T[keyof T];
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
// File field — multi-file
|
|
374
|
+
|
|
375
|
+
// getDownloadUrl + getDownloadUrls — always available (editable + readonly)
|
|
376
|
+
acc.getDownloadUrl = async (
|
|
377
|
+
attachmentId: string,
|
|
378
|
+
viewType?: AttachmentViewType,
|
|
379
|
+
): Promise<FileDownloadResponseType> => {
|
|
380
|
+
const instanceId = this._requireInstanceId();
|
|
381
|
+
return api(boId).getDownloadUrl(
|
|
382
|
+
instanceId,
|
|
383
|
+
fieldId,
|
|
384
|
+
attachmentId,
|
|
385
|
+
viewType,
|
|
386
|
+
);
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
acc.getDownloadUrls = async (
|
|
390
|
+
viewType?: AttachmentViewType,
|
|
391
|
+
): Promise<FileDownloadResponseType[]> => {
|
|
392
|
+
const instanceId = this._requireInstanceId();
|
|
393
|
+
return api(boId).getDownloadUrls(instanceId, fieldId, viewType);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// upload + deleteAttachment — editable only
|
|
397
|
+
if (!isReadOnly) {
|
|
398
|
+
/**
|
|
399
|
+
* Upload to storage and update local field value.
|
|
400
|
+
* Does NOT persist to backend — call save()/update() after uploading.
|
|
401
|
+
* (deleteAttachment is atomic — backend handles storage + DB in one call)
|
|
402
|
+
*/
|
|
403
|
+
acc.upload = async (files: File[]): Promise<FileType[]> => {
|
|
404
|
+
for (const file of files) {
|
|
405
|
+
validateFileExtension(file.name, "File");
|
|
406
|
+
}
|
|
407
|
+
const instanceId = this._requireInstanceId();
|
|
408
|
+
const requests: FileUploadRequestType[] = files.map((file) => ({
|
|
409
|
+
FileName: file.name,
|
|
410
|
+
Size: file.size,
|
|
411
|
+
FileExtension: extractFileExtension(file.name),
|
|
412
|
+
}));
|
|
413
|
+
const uploadInfos = await api(boId).getUploadUrl(
|
|
414
|
+
instanceId,
|
|
415
|
+
fieldId,
|
|
416
|
+
requests,
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
const uploaded: FileType[] = await Promise.all(
|
|
420
|
+
files.map(async (file, i) => {
|
|
421
|
+
await fetch(uploadInfos[i].UploadUrl.URL, {
|
|
422
|
+
method: "PUT",
|
|
423
|
+
headers: { "Content-Type": uploadInfos[i].ContentType },
|
|
424
|
+
body: file,
|
|
425
|
+
});
|
|
426
|
+
return {
|
|
427
|
+
_id: uploadInfos[i]._id,
|
|
428
|
+
_name: uploadInfos[i]._name,
|
|
429
|
+
FileName: uploadInfos[i].FileName,
|
|
430
|
+
FileExtension: uploadInfos[i].FileExtension,
|
|
431
|
+
Size: uploadInfos[i].Size,
|
|
432
|
+
ContentType: uploadInfos[i].ContentType,
|
|
433
|
+
};
|
|
434
|
+
}),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const current =
|
|
438
|
+
(this._data[fieldId as keyof T] as FileType[] | undefined) ?? [];
|
|
439
|
+
this._data[fieldId as keyof T] = [
|
|
440
|
+
...current,
|
|
441
|
+
...uploaded,
|
|
442
|
+
] as T[keyof T];
|
|
443
|
+
return uploaded;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
acc.deleteAttachment = async (
|
|
447
|
+
attachmentId: string,
|
|
448
|
+
): Promise<void> => {
|
|
449
|
+
const instanceId = this._requireInstanceId();
|
|
450
|
+
await api(boId).deleteAttachment(
|
|
451
|
+
instanceId,
|
|
452
|
+
fieldId,
|
|
453
|
+
attachmentId,
|
|
454
|
+
);
|
|
455
|
+
const current =
|
|
456
|
+
(this._data[fieldId as keyof T] as FileType[] | undefined) ?? [];
|
|
457
|
+
this._data[fieldId as keyof T] = current.filter(
|
|
458
|
+
(f) => f._id !== attachmentId,
|
|
459
|
+
) as T[keyof T];
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
237
465
|
// Cache and return
|
|
238
466
|
this._accessorCache.set(fieldId, accessor);
|
|
239
467
|
return accessor;
|
package/sdk/bdo/core/types.ts
CHANGED
|
@@ -142,6 +142,11 @@ export interface FileFieldMetaType extends BaseFieldMetaType {
|
|
|
142
142
|
Constraint?: BaseConstraintType;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
export interface ImageFieldMetaType extends BaseFieldMetaType {
|
|
146
|
+
Type: "Image";
|
|
147
|
+
Constraint?: BaseConstraintType;
|
|
148
|
+
}
|
|
149
|
+
|
|
145
150
|
// ============================================================
|
|
146
151
|
// RUNTIME ACCESSOR TYPES
|
|
147
152
|
// These represent what item.Title looks like at runtime
|
|
@@ -170,6 +175,64 @@ export type ReadonlyFieldAccessorType<T> = BaseFieldAccessorType<T>;
|
|
|
170
175
|
/** Union of editable or readonly accessor */
|
|
171
176
|
export type FieldAccessorType<T> = EditableFieldAccessorType<T> | ReadonlyFieldAccessorType<T>;
|
|
172
177
|
|
|
178
|
+
// ============================================================
|
|
179
|
+
// FILE/IMAGE FIELD ACCESSOR TYPES
|
|
180
|
+
// ============================================================
|
|
181
|
+
|
|
182
|
+
import type {
|
|
183
|
+
FileType,
|
|
184
|
+
ImageFieldType,
|
|
185
|
+
FileFieldType,
|
|
186
|
+
} from "../../types/base-fields";
|
|
187
|
+
import type {
|
|
188
|
+
FileDownloadResponseType,
|
|
189
|
+
AttachmentViewType,
|
|
190
|
+
} from "../../types/common";
|
|
191
|
+
|
|
192
|
+
/** Editable Image field accessor — adds upload, download, delete */
|
|
193
|
+
export interface EditableImageFieldAccessorType
|
|
194
|
+
extends EditableFieldAccessorType<ImageFieldType> {
|
|
195
|
+
upload(file: File): Promise<FileType>;
|
|
196
|
+
getDownloadUrl(
|
|
197
|
+
viewType?: AttachmentViewType,
|
|
198
|
+
): Promise<FileDownloadResponseType>;
|
|
199
|
+
deleteAttachment(): Promise<void>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Readonly Image field accessor — download only */
|
|
203
|
+
export interface ReadonlyImageFieldAccessorType
|
|
204
|
+
extends ReadonlyFieldAccessorType<ImageFieldType> {
|
|
205
|
+
getDownloadUrl(
|
|
206
|
+
viewType?: AttachmentViewType,
|
|
207
|
+
): Promise<FileDownloadResponseType>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Editable File field accessor — adds upload, download, delete */
|
|
211
|
+
export interface EditableFileFieldAccessorType
|
|
212
|
+
extends EditableFieldAccessorType<FileFieldType> {
|
|
213
|
+
upload(files: File[]): Promise<FileType[]>;
|
|
214
|
+
getDownloadUrl(
|
|
215
|
+
attachmentId: string,
|
|
216
|
+
viewType?: AttachmentViewType,
|
|
217
|
+
): Promise<FileDownloadResponseType>;
|
|
218
|
+
getDownloadUrls(
|
|
219
|
+
viewType?: AttachmentViewType,
|
|
220
|
+
): Promise<FileDownloadResponseType[]>;
|
|
221
|
+
deleteAttachment(attachmentId: string): Promise<void>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Readonly File field accessor — download only */
|
|
225
|
+
export interface ReadonlyFileFieldAccessorType
|
|
226
|
+
extends ReadonlyFieldAccessorType<FileFieldType> {
|
|
227
|
+
getDownloadUrl(
|
|
228
|
+
attachmentId: string,
|
|
229
|
+
viewType?: AttachmentViewType,
|
|
230
|
+
): Promise<FileDownloadResponseType>;
|
|
231
|
+
getDownloadUrls(
|
|
232
|
+
viewType?: AttachmentViewType,
|
|
233
|
+
): Promise<FileDownloadResponseType[]>;
|
|
234
|
+
}
|
|
235
|
+
|
|
173
236
|
// ============================================================
|
|
174
237
|
// SELECT FIELD OPTIONS
|
|
175
238
|
// ============================================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ============================================================
|
|
2
2
|
// FILE FIELD
|
|
3
|
-
// Field for file attachments
|
|
3
|
+
// Field for file attachments (array of files)
|
|
4
4
|
// ============================================================
|
|
5
5
|
|
|
6
6
|
import type { FileFieldType } from "../../types/base-fields";
|
|
@@ -12,8 +12,8 @@ import { BaseField } from "./BaseField";
|
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* ```typescript
|
|
15
|
-
* readonly
|
|
16
|
-
* _id: "
|
|
15
|
+
* readonly Attachments = new FileField({
|
|
16
|
+
* _id: "Attachments", Name: "Attachments", Type: "File",
|
|
17
17
|
* });
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
@@ -27,13 +27,22 @@ export class FileField extends BaseField<FileFieldType> {
|
|
|
27
27
|
return { valid: true, errors: [] };
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
if (
|
|
30
|
+
if (!Array.isArray(value)) {
|
|
31
31
|
return {
|
|
32
32
|
valid: false,
|
|
33
|
-
errors: [`${this.label} must be
|
|
33
|
+
errors: [`${this.label} must be an array of file objects`],
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
for (const item of value) {
|
|
38
|
+
if (!item || typeof item !== "object" || !item._id) {
|
|
39
|
+
return {
|
|
40
|
+
valid: false,
|
|
41
|
+
errors: [`Each file in ${this.label} must have an _id`],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
return { valid: true, errors: [] };
|
|
38
47
|
}
|
|
39
48
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// IMAGE FIELD
|
|
3
|
+
// Field for single image attachments (nullable)
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
import type { ImageFieldType } from "../../types/base-fields";
|
|
7
|
+
import type { ImageFieldMetaType, ValidationResultType } from "../core/types";
|
|
8
|
+
import { BaseField } from "./BaseField";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Field definition for single image attachment fields
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* readonly Avatar = new ImageField({
|
|
16
|
+
* _id: "Avatar", Name: "Avatar", Type: "Image",
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class ImageField extends BaseField<ImageFieldType> {
|
|
21
|
+
constructor(meta: ImageFieldMetaType) {
|
|
22
|
+
super(meta);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validate(value: ImageFieldType | undefined): ValidationResultType {
|
|
26
|
+
if (value === undefined || value === null) {
|
|
27
|
+
return { valid: true, errors: [] };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
31
|
+
return {
|
|
32
|
+
valid: false,
|
|
33
|
+
errors: [`${this.label} must be a valid image object`],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!value._id || !value.FileName) {
|
|
38
|
+
return {
|
|
39
|
+
valid: false,
|
|
40
|
+
errors: [`${this.label} must have _id and FileName`],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { valid: true, errors: [] };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Supported image extensions — matches backend SupportFileExtensions.IMAGE_EXTENSIONS */
|
|
2
|
+
export const IMAGE_EXTENSIONS = new Set([
|
|
3
|
+
"jpg",
|
|
4
|
+
"jpeg",
|
|
5
|
+
"png",
|
|
6
|
+
"gif",
|
|
7
|
+
"webp",
|
|
8
|
+
"bmp",
|
|
9
|
+
"tiff",
|
|
10
|
+
"tif",
|
|
11
|
+
"heic",
|
|
12
|
+
"heif",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
/** Supported file extensions — matches backend SupportFileExtensions.FILE_EXTENSIONS */
|
|
16
|
+
export const FILE_EXTENSIONS = new Set([
|
|
17
|
+
// Images
|
|
18
|
+
"jpg",
|
|
19
|
+
"jpeg",
|
|
20
|
+
"png",
|
|
21
|
+
"gif",
|
|
22
|
+
"webp",
|
|
23
|
+
"bmp",
|
|
24
|
+
"tiff",
|
|
25
|
+
"tif",
|
|
26
|
+
"heic",
|
|
27
|
+
"heif",
|
|
28
|
+
// Videos
|
|
29
|
+
"mp4",
|
|
30
|
+
"mov",
|
|
31
|
+
"avi",
|
|
32
|
+
"webm",
|
|
33
|
+
"mkv",
|
|
34
|
+
"m4v",
|
|
35
|
+
"wmv",
|
|
36
|
+
"flv",
|
|
37
|
+
// Documents
|
|
38
|
+
"pdf",
|
|
39
|
+
"doc",
|
|
40
|
+
"docx",
|
|
41
|
+
"xls",
|
|
42
|
+
"xlsx",
|
|
43
|
+
"ppt",
|
|
44
|
+
"pptx",
|
|
45
|
+
// Other
|
|
46
|
+
"txt",
|
|
47
|
+
"csv",
|
|
48
|
+
"zip",
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
/** Extract and normalize file extension from a filename. Returns lowercase without dot. */
|
|
52
|
+
export function extractFileExtension(fileName: string): string {
|
|
53
|
+
if (!fileName.includes(".")) return "";
|
|
54
|
+
const parts = fileName.split(".");
|
|
55
|
+
return (parts[parts.length - 1] ?? "").toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Validate file extension against backend whitelist. Throws on invalid. */
|
|
59
|
+
export function validateFileExtension(
|
|
60
|
+
fileName: string,
|
|
61
|
+
fieldType: "File" | "Image",
|
|
62
|
+
): void {
|
|
63
|
+
const ext = extractFileExtension(fileName);
|
|
64
|
+
const allowed = fieldType === "Image" ? IMAGE_EXTENSIONS : FILE_EXTENSIONS;
|
|
65
|
+
if (!ext || !allowed.has(ext)) {
|
|
66
|
+
const types = [...allowed].sort().join(", ");
|
|
67
|
+
throw new Error(
|
|
68
|
+
`File "${fileName}" has unsupported extension "${ext || "(none)"}". ` +
|
|
69
|
+
`Supported for ${fieldType} fields: ${types}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
package/sdk/bdo/fields/index.ts
CHANGED
|
@@ -15,4 +15,5 @@ export { ObjectField } from "./ObjectField";
|
|
|
15
15
|
export { TextField } from "./TextField";
|
|
16
16
|
export { UserField } from "./UserField";
|
|
17
17
|
export { FileField } from "./FileField";
|
|
18
|
+
export { ImageField } from "./ImageField";
|
|
18
19
|
export { TextAreaField } from "./TextAreaField";
|
package/sdk/bdo/index.ts
CHANGED
|
@@ -32,7 +32,12 @@ export type {
|
|
|
32
32
|
ArrayFieldMetaType,
|
|
33
33
|
ObjectFieldMetaType,
|
|
34
34
|
FileFieldMetaType,
|
|
35
|
+
ImageFieldMetaType,
|
|
35
36
|
BaseFieldAccessorType,
|
|
37
|
+
EditableImageFieldAccessorType,
|
|
38
|
+
ReadonlyImageFieldAccessorType,
|
|
39
|
+
EditableFileFieldAccessorType,
|
|
40
|
+
ReadonlyFileFieldAccessorType,
|
|
36
41
|
} from "./core/types";
|
|
37
42
|
|
|
38
43
|
// Field classes
|
|
@@ -51,4 +56,5 @@ export {
|
|
|
51
56
|
ObjectField,
|
|
52
57
|
UserField,
|
|
53
58
|
FileField,
|
|
59
|
+
ImageField,
|
|
54
60
|
} from "./fields";
|
package/sdk/bdo.ts
CHANGED
package/sdk/bdo.types.ts
CHANGED
|
@@ -23,10 +23,15 @@ export type {
|
|
|
23
23
|
ArrayFieldMetaType,
|
|
24
24
|
ObjectFieldMetaType,
|
|
25
25
|
FileFieldMetaType,
|
|
26
|
+
ImageFieldMetaType,
|
|
26
27
|
BaseFieldAccessorType,
|
|
27
28
|
EditableFieldAccessorType,
|
|
28
29
|
ReadonlyFieldAccessorType,
|
|
29
30
|
FieldAccessorType,
|
|
31
|
+
EditableImageFieldAccessorType,
|
|
32
|
+
ReadonlyImageFieldAccessorType,
|
|
33
|
+
EditableFileFieldAccessorType,
|
|
34
|
+
ReadonlyFileFieldAccessorType,
|
|
30
35
|
} from "./bdo/core/types";
|
|
31
36
|
|
|
32
37
|
// Re-export SDK field types
|
|
@@ -43,6 +48,8 @@ export type {
|
|
|
43
48
|
ArrayFieldType,
|
|
44
49
|
ObjectFieldType,
|
|
45
50
|
UserFieldType,
|
|
51
|
+
FileType,
|
|
52
|
+
ImageFieldType,
|
|
46
53
|
FileFieldType,
|
|
47
54
|
SystemFieldsType,
|
|
48
55
|
UserRefType,
|
|
@@ -10,7 +10,7 @@ export function validateConstraints(field: BaseField<unknown>, value: unknown):
|
|
|
10
10
|
const errors: string[] = [];
|
|
11
11
|
|
|
12
12
|
// Required
|
|
13
|
-
if (field.required && (value === undefined || value === null || value === "")) {
|
|
13
|
+
if (field.required && (value === undefined || value === null || value === "" || (Array.isArray(value) && value.length === 0))) {
|
|
14
14
|
errors.push(`${field.label} is required`);
|
|
15
15
|
return { valid: false, errors };
|
|
16
16
|
}
|