@deepraxis/sdk 0.1.0-dev.5e0f28e → 0.1.0-dev.6898104
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/METHODS.md +15 -1
- package/dist/core/client.d.ts +5 -1
- package/dist/core/client.js +3 -0
- package/dist/generated/index.d.ts +2 -2
- package/dist/generated/index.js +1 -1
- package/dist/generated/sdk.gen.d.ts +28 -4
- package/dist/generated/sdk.gen.js +72 -3
- package/dist/generated/types.gen.d.ts +280 -19
- package/dist/helpers/file-upload.d.ts +24 -0
- package/dist/helpers/file-upload.js +113 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/operations.generated.d.ts +122 -4
- package/dist/operations.generated.js +150 -4
- package/dist/resources.generated.d.ts +8 -0
- package/dist/resources.generated.js +8 -0
- package/package.json +1 -1
|
@@ -714,7 +714,7 @@ export type BrowserStreamMessage = {
|
|
|
714
714
|
};
|
|
715
715
|
export type OwnFileCreateFileRequest = {
|
|
716
716
|
/**
|
|
717
|
-
* File bytes in one multipart file part, not a filename or file ID. The file must be nonempty and at most
|
|
717
|
+
* File bytes in one multipart file part, not a filename or file ID. The file must be nonempty and at most 100000000 bytes; its content determines the MIME type.
|
|
718
718
|
*/
|
|
719
719
|
file: Blob | File;
|
|
720
720
|
/**
|
|
@@ -739,7 +739,7 @@ export type OwnFileCreateFileRequest = {
|
|
|
739
739
|
*/
|
|
740
740
|
export type OwnFileOpenAiFile = {
|
|
741
741
|
/**
|
|
742
|
-
* File identifier:
|
|
742
|
+
* File identifier: file- followed by 24 base62 characters. Use the ID returned by upload or list.
|
|
743
743
|
*/
|
|
744
744
|
id: string;
|
|
745
745
|
/**
|
|
@@ -753,7 +753,7 @@ export type OwnFileOpenAiFile = {
|
|
|
753
753
|
/**
|
|
754
754
|
* The Unix timestamp (in seconds) for when the file will expire.
|
|
755
755
|
*/
|
|
756
|
-
expires_at?: number;
|
|
756
|
+
expires_at?: number | null;
|
|
757
757
|
/**
|
|
758
758
|
* The name of the file.
|
|
759
759
|
*/
|
|
@@ -772,22 +772,12 @@ export type OwnFileOpenAiFile = {
|
|
|
772
772
|
* @deprecated
|
|
773
773
|
*/
|
|
774
774
|
status: 'uploaded' | 'processed' | 'error';
|
|
775
|
-
/**
|
|
776
|
-
* Deprecated; always null or absent.
|
|
777
|
-
*
|
|
778
|
-
* @deprecated
|
|
779
|
-
*/
|
|
780
|
-
status_details?: string | null;
|
|
781
775
|
/**
|
|
782
776
|
* Content-detected MIME type.
|
|
783
777
|
*/
|
|
784
778
|
mime_type: string;
|
|
785
779
|
/**
|
|
786
|
-
*
|
|
787
|
-
*/
|
|
788
|
-
downloadable: true;
|
|
789
|
-
/**
|
|
790
|
-
* Present for session output files.
|
|
780
|
+
* Session scope for session outputs; null for other files.
|
|
791
781
|
*/
|
|
792
782
|
scope?: {
|
|
793
783
|
/**
|
|
@@ -798,7 +788,7 @@ export type OwnFileOpenAiFile = {
|
|
|
798
788
|
* Id of the session the file belongs to.
|
|
799
789
|
*/
|
|
800
790
|
id: string;
|
|
801
|
-
};
|
|
791
|
+
} | null;
|
|
802
792
|
/**
|
|
803
793
|
* Path within the session output directory; present for session outputs.
|
|
804
794
|
*/
|
|
@@ -807,6 +797,18 @@ export type OwnFileOpenAiFile = {
|
|
|
807
797
|
* Session output revision; present for session outputs.
|
|
808
798
|
*/
|
|
809
799
|
revision?: number;
|
|
800
|
+
/**
|
|
801
|
+
* processing is not yet usable; active is ready; expired cannot be read.
|
|
802
|
+
*/
|
|
803
|
+
state: 'processing' | 'active' | 'expired';
|
|
804
|
+
/**
|
|
805
|
+
* upload is user-provided, generated is platform-produced, session is a session output.
|
|
806
|
+
*/
|
|
807
|
+
source: 'upload' | 'generated' | 'session';
|
|
808
|
+
/**
|
|
809
|
+
* Content digest, prefixed sha256:. Available after processing completes.
|
|
810
|
+
*/
|
|
811
|
+
sha256: string;
|
|
810
812
|
};
|
|
811
813
|
export type OwnFileErrorResponse = {
|
|
812
814
|
error: OwnFileError;
|
|
@@ -835,7 +837,7 @@ export type OwnFileListFilesResponse = {
|
|
|
835
837
|
};
|
|
836
838
|
export type OwnFileDeleteFileResponse = {
|
|
837
839
|
/**
|
|
838
|
-
* File identifier:
|
|
840
|
+
* File identifier: file- followed by 24 base62 characters. Use the ID returned by upload or list.
|
|
839
841
|
*/
|
|
840
842
|
id: string;
|
|
841
843
|
/**
|
|
@@ -847,6 +849,132 @@ export type OwnFileDeleteFileResponse = {
|
|
|
847
849
|
*/
|
|
848
850
|
deleted: boolean;
|
|
849
851
|
};
|
|
852
|
+
export type OwnFilePraxisCreateUpload = {
|
|
853
|
+
/**
|
|
854
|
+
* Exact complete file length in bytes, from 1 through 8000000000; the sum of completed parts must match.
|
|
855
|
+
*/
|
|
856
|
+
bytes: number;
|
|
857
|
+
/**
|
|
858
|
+
* Filename for the resulting file, at most 255 UTF-8 bytes.
|
|
859
|
+
*/
|
|
860
|
+
filename: string;
|
|
861
|
+
/**
|
|
862
|
+
* Declared MIME type; validated against the assembled content.
|
|
863
|
+
*/
|
|
864
|
+
mime_type: string;
|
|
865
|
+
/**
|
|
866
|
+
* Use user_data for conversation attachments, assistants for assistant files, or vision for PNG, JPEG, GIF and WebP images.
|
|
867
|
+
*/
|
|
868
|
+
purpose: 'user_data' | 'assistants' | 'vision';
|
|
869
|
+
/**
|
|
870
|
+
* Optional expiry policy for the resulting file; omission retains the file until deletion.
|
|
871
|
+
*/
|
|
872
|
+
expires_after?: {
|
|
873
|
+
/**
|
|
874
|
+
* Expiry is measured from file creation; created_at is the only supported anchor.
|
|
875
|
+
*/
|
|
876
|
+
anchor?: 'created_at';
|
|
877
|
+
/**
|
|
878
|
+
* Resulting file lifetime in seconds; omit expires_after to retain it until deletion.
|
|
879
|
+
*/
|
|
880
|
+
seconds: number;
|
|
881
|
+
};
|
|
882
|
+
/**
|
|
883
|
+
* Optional fixed part size in bytes, 5242880–64000000. Requires part_number on every part; retries of identical bytes at the same number return the same part. Omit to use arbitrary-sized parts.
|
|
884
|
+
*/
|
|
885
|
+
part_size?: number;
|
|
886
|
+
};
|
|
887
|
+
export type OwnFilePraxisUpload = {
|
|
888
|
+
/**
|
|
889
|
+
* Upload identifier returned by create. Use this value for parts, complete and cancel.
|
|
890
|
+
*/
|
|
891
|
+
id: string;
|
|
892
|
+
/**
|
|
893
|
+
* Object type; upload identifies an upload session.
|
|
894
|
+
*/
|
|
895
|
+
object: 'upload';
|
|
896
|
+
/**
|
|
897
|
+
* Declared size of the complete file in bytes.
|
|
898
|
+
*/
|
|
899
|
+
bytes: number;
|
|
900
|
+
/**
|
|
901
|
+
* Creation time as Unix seconds.
|
|
902
|
+
*/
|
|
903
|
+
created_at: number;
|
|
904
|
+
/**
|
|
905
|
+
* Expiry time as Unix seconds; unfinished uploads expire after one hour.
|
|
906
|
+
*/
|
|
907
|
+
expires_at: number;
|
|
908
|
+
/**
|
|
909
|
+
* Filename supplied when creating the upload.
|
|
910
|
+
*/
|
|
911
|
+
filename: string;
|
|
912
|
+
/**
|
|
913
|
+
* File purpose: user_data for conversation attachments, assistants for assistant files, vision for images.
|
|
914
|
+
*/
|
|
915
|
+
purpose: 'user_data' | 'assistants' | 'vision';
|
|
916
|
+
/**
|
|
917
|
+
* Upload state: pending accepts parts; completed has a file; cancelled was cancelled; expired exceeded its one-hour lifetime.
|
|
918
|
+
*/
|
|
919
|
+
status: 'pending' | 'completed' | 'cancelled' | 'expired';
|
|
920
|
+
/**
|
|
921
|
+
* Present after complete. A file with status uploaded is still processing; poll files.retrieve until processed before use.
|
|
922
|
+
*/
|
|
923
|
+
file?: OwnFileOpenAiFile | null;
|
|
924
|
+
/**
|
|
925
|
+
* Declared MIME type, verified during assembly.
|
|
926
|
+
*/
|
|
927
|
+
mime_type?: string;
|
|
928
|
+
/**
|
|
929
|
+
* Assembly failure, or null if no failure is recorded.
|
|
930
|
+
*/
|
|
931
|
+
error?: {
|
|
932
|
+
/**
|
|
933
|
+
* File service error code identifying the assembly failure.
|
|
934
|
+
*/
|
|
935
|
+
code: string;
|
|
936
|
+
/**
|
|
937
|
+
* Explanation of why assembly failed.
|
|
938
|
+
*/
|
|
939
|
+
message: string;
|
|
940
|
+
} | null;
|
|
941
|
+
};
|
|
942
|
+
export type OwnFilePraxisUploadPart = {
|
|
943
|
+
/**
|
|
944
|
+
* Part identifier returned by this request. Supply it to complete in file byte order.
|
|
945
|
+
*/
|
|
946
|
+
id: string;
|
|
947
|
+
/**
|
|
948
|
+
* Object type; upload.part identifies an uploaded part.
|
|
949
|
+
*/
|
|
950
|
+
object: 'upload.part';
|
|
951
|
+
/**
|
|
952
|
+
* Part creation time as Unix seconds.
|
|
953
|
+
*/
|
|
954
|
+
created_at: number;
|
|
955
|
+
/**
|
|
956
|
+
* Parent upload identifier supplied in the request path.
|
|
957
|
+
*/
|
|
958
|
+
upload_id: string;
|
|
959
|
+
/**
|
|
960
|
+
* Accepted part length in bytes.
|
|
961
|
+
*/
|
|
962
|
+
bytes?: number;
|
|
963
|
+
/**
|
|
964
|
+
* Part content digest prefixed sha256:.
|
|
965
|
+
*/
|
|
966
|
+
sha256?: string;
|
|
967
|
+
};
|
|
968
|
+
export type OwnFilePraxisCompleteUpload = {
|
|
969
|
+
/**
|
|
970
|
+
* All part IDs in concatenation order, without duplicates. Fixed-size uploads require increasing part_number order.
|
|
971
|
+
*/
|
|
972
|
+
part_ids: Array<string>;
|
|
973
|
+
/**
|
|
974
|
+
* Optional whole-file MD5 as 32 hexadecimal characters; verified after assembly.
|
|
975
|
+
*/
|
|
976
|
+
md5?: string;
|
|
977
|
+
};
|
|
850
978
|
/**
|
|
851
979
|
* Problem document returned by the Meta method catalog on failure.
|
|
852
980
|
*/
|
|
@@ -34421,7 +34549,7 @@ export type FileDeleteFileData = {
|
|
|
34421
34549
|
body?: never;
|
|
34422
34550
|
path: {
|
|
34423
34551
|
/**
|
|
34424
|
-
* ID of a tenant-owned live file, as returned by upload or list; starts with
|
|
34552
|
+
* ID of a tenant-owned live file, as returned by upload or list; starts with file-.
|
|
34425
34553
|
*/
|
|
34426
34554
|
file_id: string;
|
|
34427
34555
|
};
|
|
@@ -34450,7 +34578,7 @@ export type FileRetrieveFileData = {
|
|
|
34450
34578
|
body?: never;
|
|
34451
34579
|
path: {
|
|
34452
34580
|
/**
|
|
34453
|
-
* ID of a tenant-owned live file, as returned by upload or list; starts with
|
|
34581
|
+
* ID of a tenant-owned live file, as returned by upload or list; starts with file-.
|
|
34454
34582
|
*/
|
|
34455
34583
|
file_id: string;
|
|
34456
34584
|
};
|
|
@@ -34479,7 +34607,7 @@ export type FileRetrieveFileContentData = {
|
|
|
34479
34607
|
body?: never;
|
|
34480
34608
|
path: {
|
|
34481
34609
|
/**
|
|
34482
|
-
* ID of a tenant-owned live file, as returned by upload or list; starts with
|
|
34610
|
+
* ID of a tenant-owned live file, as returned by upload or list; starts with file-.
|
|
34483
34611
|
*/
|
|
34484
34612
|
file_id: string;
|
|
34485
34613
|
};
|
|
@@ -34512,6 +34640,139 @@ export type FileRetrieveFileContentResponses = {
|
|
|
34512
34640
|
200: Blob | File;
|
|
34513
34641
|
};
|
|
34514
34642
|
export type FileRetrieveFileContentResponse = FileRetrieveFileContentResponses[keyof FileRetrieveFileContentResponses];
|
|
34643
|
+
export type FileCreateUploadData = {
|
|
34644
|
+
body: OwnFilePraxisCreateUpload;
|
|
34645
|
+
path?: never;
|
|
34646
|
+
query?: never;
|
|
34647
|
+
url: '/file/v1/uploads';
|
|
34648
|
+
};
|
|
34649
|
+
export type FileCreateUploadErrors = {
|
|
34650
|
+
/**
|
|
34651
|
+
* Invalid upload input or upload state; correct the request before retrying.
|
|
34652
|
+
*/
|
|
34653
|
+
400: unknown;
|
|
34654
|
+
/**
|
|
34655
|
+
* The upload is missing or belongs to another organization.
|
|
34656
|
+
*/
|
|
34657
|
+
404: unknown;
|
|
34658
|
+
/**
|
|
34659
|
+
* Completion inputs conflict with an earlier completion request.
|
|
34660
|
+
*/
|
|
34661
|
+
409: unknown;
|
|
34662
|
+
};
|
|
34663
|
+
export type FileCreateUploadResponses = {
|
|
34664
|
+
/**
|
|
34665
|
+
* The upload state and, after completion, the resulting file.
|
|
34666
|
+
*/
|
|
34667
|
+
200: OwnFilePraxisUpload;
|
|
34668
|
+
};
|
|
34669
|
+
export type FileCreateUploadResponse = FileCreateUploadResponses[keyof FileCreateUploadResponses];
|
|
34670
|
+
export type FileAddUploadPartData = {
|
|
34671
|
+
body: {
|
|
34672
|
+
/**
|
|
34673
|
+
* Raw bytes of this part, nonempty and at most 64000000 bytes.
|
|
34674
|
+
*/
|
|
34675
|
+
data: Blob | File;
|
|
34676
|
+
};
|
|
34677
|
+
path: {
|
|
34678
|
+
/**
|
|
34679
|
+
* Upload identifier from create.
|
|
34680
|
+
*/
|
|
34681
|
+
upload_id: string;
|
|
34682
|
+
};
|
|
34683
|
+
query?: {
|
|
34684
|
+
/**
|
|
34685
|
+
* Fixed part position, starting at 1.
|
|
34686
|
+
*/
|
|
34687
|
+
part_number?: number;
|
|
34688
|
+
};
|
|
34689
|
+
url: '/file/v1/uploads/{upload_id}/parts';
|
|
34690
|
+
};
|
|
34691
|
+
export type FileAddUploadPartErrors = {
|
|
34692
|
+
/**
|
|
34693
|
+
* Invalid upload input or upload state; correct the request before retrying.
|
|
34694
|
+
*/
|
|
34695
|
+
400: unknown;
|
|
34696
|
+
/**
|
|
34697
|
+
* The upload is missing or belongs to another organization.
|
|
34698
|
+
*/
|
|
34699
|
+
404: unknown;
|
|
34700
|
+
/**
|
|
34701
|
+
* Completion inputs conflict with an earlier completion request.
|
|
34702
|
+
*/
|
|
34703
|
+
409: unknown;
|
|
34704
|
+
};
|
|
34705
|
+
export type FileAddUploadPartResponses = {
|
|
34706
|
+
/**
|
|
34707
|
+
* The part was accepted.
|
|
34708
|
+
*/
|
|
34709
|
+
200: OwnFilePraxisUploadPart;
|
|
34710
|
+
};
|
|
34711
|
+
export type FileAddUploadPartResponse = FileAddUploadPartResponses[keyof FileAddUploadPartResponses];
|
|
34712
|
+
export type FileCompleteUploadData = {
|
|
34713
|
+
body: OwnFilePraxisCompleteUpload;
|
|
34714
|
+
path: {
|
|
34715
|
+
/**
|
|
34716
|
+
* Upload identifier from create.
|
|
34717
|
+
*/
|
|
34718
|
+
upload_id: string;
|
|
34719
|
+
};
|
|
34720
|
+
query?: never;
|
|
34721
|
+
url: '/file/v1/uploads/{upload_id}/complete';
|
|
34722
|
+
};
|
|
34723
|
+
export type FileCompleteUploadErrors = {
|
|
34724
|
+
/**
|
|
34725
|
+
* Invalid upload input or upload state; correct the request before retrying.
|
|
34726
|
+
*/
|
|
34727
|
+
400: unknown;
|
|
34728
|
+
/**
|
|
34729
|
+
* The upload is missing or belongs to another organization.
|
|
34730
|
+
*/
|
|
34731
|
+
404: unknown;
|
|
34732
|
+
/**
|
|
34733
|
+
* Completion inputs conflict with an earlier completion request.
|
|
34734
|
+
*/
|
|
34735
|
+
409: unknown;
|
|
34736
|
+
};
|
|
34737
|
+
export type FileCompleteUploadResponses = {
|
|
34738
|
+
/**
|
|
34739
|
+
* The upload state and, after completion, the resulting file.
|
|
34740
|
+
*/
|
|
34741
|
+
200: OwnFilePraxisUpload;
|
|
34742
|
+
};
|
|
34743
|
+
export type FileCompleteUploadResponse = FileCompleteUploadResponses[keyof FileCompleteUploadResponses];
|
|
34744
|
+
export type FileCancelUploadData = {
|
|
34745
|
+
body?: never;
|
|
34746
|
+
path: {
|
|
34747
|
+
/**
|
|
34748
|
+
* Upload identifier from create.
|
|
34749
|
+
*/
|
|
34750
|
+
upload_id: string;
|
|
34751
|
+
};
|
|
34752
|
+
query?: never;
|
|
34753
|
+
url: '/file/v1/uploads/{upload_id}/cancel';
|
|
34754
|
+
};
|
|
34755
|
+
export type FileCancelUploadErrors = {
|
|
34756
|
+
/**
|
|
34757
|
+
* Invalid upload input or upload state; correct the request before retrying.
|
|
34758
|
+
*/
|
|
34759
|
+
400: unknown;
|
|
34760
|
+
/**
|
|
34761
|
+
* The upload is missing or belongs to another organization.
|
|
34762
|
+
*/
|
|
34763
|
+
404: unknown;
|
|
34764
|
+
/**
|
|
34765
|
+
* Completion inputs conflict with an earlier completion request.
|
|
34766
|
+
*/
|
|
34767
|
+
409: unknown;
|
|
34768
|
+
};
|
|
34769
|
+
export type FileCancelUploadResponses = {
|
|
34770
|
+
/**
|
|
34771
|
+
* The upload state and, after completion, the resulting file.
|
|
34772
|
+
*/
|
|
34773
|
+
200: OwnFilePraxisUpload;
|
|
34774
|
+
};
|
|
34775
|
+
export type FileCancelUploadResponse = FileCancelUploadResponses[keyof FileCancelUploadResponses];
|
|
34515
34776
|
export type HealthCheckData = {
|
|
34516
34777
|
body?: never;
|
|
34517
34778
|
path?: never;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Praxis } from '../core/client.js';
|
|
2
|
+
import type { OwnFileOpenAiFile } from '../generated/types.gen.js';
|
|
3
|
+
export interface UploadFileOptions {
|
|
4
|
+
/** user_data for conversation attachments; assistants for assistant files; vision for PNG/JPEG/GIF/WebP. */
|
|
5
|
+
purpose: 'user_data' | 'assistants' | 'vision';
|
|
6
|
+
/** Defaults to File.name, or upload for a Blob. */
|
|
7
|
+
filename?: string;
|
|
8
|
+
/** Fixed part size in bytes, 5 MiB–64 MB; defaults to 50 MiB. */
|
|
9
|
+
partSize?: number;
|
|
10
|
+
/** Concurrent part requests, a positive integer; defaults to 3. */
|
|
11
|
+
concurrency?: number;
|
|
12
|
+
/** Aborts outstanding requests and cancels an unfinished Upload. */
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
/** Acknowledged byte fraction, 0–1. A value of 1 does not imply processing has finished. */
|
|
15
|
+
onProgress?: (fraction: number) => void;
|
|
16
|
+
}
|
|
17
|
+
export type UploadedFile = OwnFileOpenAiFile;
|
|
18
|
+
/** Upload a nonempty file up to 2 GiB, returning only after processing reaches active.
|
|
19
|
+
* Files up to 32 MiB use one request; larger files use fixed-size Uploads parts.
|
|
20
|
+
* Parts and complete each retry transient failures at most five times with exponential backoff.
|
|
21
|
+
* A terminal failure or abort cancels the Upload. Completed files cannot be cancelled;
|
|
22
|
+
* if abort occurs during processing, delete the resulting file explicitly if no longer needed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function uploadFile(client: Pick<Praxis, 'file'>, file: Blob | File, options: UploadFileOptions): Promise<UploadedFile>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { PraxisError, PraxisConnectionError, PraxisTimeoutError } from "../core/errors.js";
|
|
2
|
+
import { sleep } from "../core/retry.js";
|
|
3
|
+
const MAX_RETRIES = 5;
|
|
4
|
+
function retryable(error) {
|
|
5
|
+
return error instanceof PraxisConnectionError || error instanceof PraxisTimeoutError
|
|
6
|
+
|| error instanceof SyntaxError
|
|
7
|
+
|| (error instanceof PraxisError && (error.status === 408 || error.status === 429 || (error.status !== undefined && error.status >= 500 && error.status <= 599)));
|
|
8
|
+
}
|
|
9
|
+
async function retry(signal, operation) {
|
|
10
|
+
for (let used = 0;; used++) {
|
|
11
|
+
signal.throwIfAborted();
|
|
12
|
+
try {
|
|
13
|
+
return await operation();
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
signal.throwIfAborted();
|
|
17
|
+
if (used >= MAX_RETRIES || !retryable(error))
|
|
18
|
+
throw error;
|
|
19
|
+
await sleep(500 * 2 ** used, signal);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Upload a nonempty file up to 2 GiB, returning only after processing reaches active.
|
|
24
|
+
* Files up to 32 MiB use one request; larger files use fixed-size Uploads parts.
|
|
25
|
+
* Parts and complete each retry transient failures at most five times with exponential backoff.
|
|
26
|
+
* A terminal failure or abort cancels the Upload. Completed files cannot be cancelled;
|
|
27
|
+
* if abort occurs during processing, delete the resulting file explicitly if no longer needed.
|
|
28
|
+
*/
|
|
29
|
+
export async function uploadFile(client, file, options) {
|
|
30
|
+
options.signal?.throwIfAborted();
|
|
31
|
+
if (file.size < 1 || file.size > 2 * 1024 ** 3)
|
|
32
|
+
throw new RangeError('File size must be 1 byte through 2 GiB');
|
|
33
|
+
const partSize = options.partSize ?? 50 * 1024 ** 2;
|
|
34
|
+
const concurrency = options.concurrency ?? 3;
|
|
35
|
+
if (!Number.isSafeInteger(partSize) || partSize < 5 * 1024 ** 2 || partSize > 64_000_000)
|
|
36
|
+
throw new RangeError('partSize must be an integer from 5242880 through 64000000');
|
|
37
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1)
|
|
38
|
+
throw new RangeError('concurrency must be a positive integer');
|
|
39
|
+
const filename = options.filename ?? (typeof File !== 'undefined' && file instanceof File ? file.name : 'upload');
|
|
40
|
+
const work = new AbortController();
|
|
41
|
+
const abort = () => work.abort(options.signal?.reason);
|
|
42
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
43
|
+
const signal = work.signal;
|
|
44
|
+
const request = { signal, maxRetries: 0, timeoutMs: 120_000 };
|
|
45
|
+
let uploadId;
|
|
46
|
+
const active = async (initial) => {
|
|
47
|
+
let result = initial;
|
|
48
|
+
for (let polls = 0;; polls++) {
|
|
49
|
+
signal.throwIfAborted();
|
|
50
|
+
if (result.state === 'active')
|
|
51
|
+
return result;
|
|
52
|
+
if (result.state !== 'processing')
|
|
53
|
+
throw new PraxisError({ code: 'FILE_PROCESSING_FAILED', message: `File ${result.id} is not active (${result.state})` });
|
|
54
|
+
await sleep(Math.min(5000, 1000 * 2 ** polls), signal);
|
|
55
|
+
result = await retry(signal, () => client.file.files.retrieve({ path: { file_id: result.id }, ...request }));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
signal.throwIfAborted();
|
|
60
|
+
options.onProgress?.(0);
|
|
61
|
+
if (file.size <= 32 * 1024 ** 2) {
|
|
62
|
+
const result = await client.file.files.create({ body: { file: new File([file], filename, { type: file.type }), purpose: options.purpose }, ...request });
|
|
63
|
+
options.onProgress?.(1);
|
|
64
|
+
return await active(result);
|
|
65
|
+
}
|
|
66
|
+
const upload = await client.file.uploads.create({ body: { bytes: file.size, filename, mime_type: file.type || 'application/octet-stream', purpose: options.purpose, part_size: partSize }, ...request });
|
|
67
|
+
uploadId = upload.id;
|
|
68
|
+
signal.throwIfAborted();
|
|
69
|
+
const path = { upload_id: uploadId };
|
|
70
|
+
const count = Math.ceil(file.size / partSize);
|
|
71
|
+
const ids = new Array(count);
|
|
72
|
+
let cursor = 0, confirmed = 0;
|
|
73
|
+
const worker = async () => {
|
|
74
|
+
while (cursor < count) {
|
|
75
|
+
signal.throwIfAborted();
|
|
76
|
+
const index = cursor++;
|
|
77
|
+
const chunk = file.slice(index * partSize, Math.min(file.size, (index + 1) * partSize));
|
|
78
|
+
const part = await retry(signal, () => client.file.uploads.parts.create({ path, query: { part_number: index + 1 }, body: { data: new File([chunk], filename) }, ...request }));
|
|
79
|
+
ids[index] = part.id;
|
|
80
|
+
confirmed += chunk.size;
|
|
81
|
+
options.onProgress?.(confirmed / file.size);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const workers = Array.from({ length: Math.min(concurrency, count) }, worker);
|
|
85
|
+
try {
|
|
86
|
+
await Promise.all(workers);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
work.abort();
|
|
90
|
+
await Promise.allSettled(workers);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
const result = await retry(signal, () => client.file.uploads.complete({ path, body: { part_ids: ids }, ...request }));
|
|
94
|
+
if (result.error)
|
|
95
|
+
throw new PraxisError({ code: result.error.code, message: result.error.message });
|
|
96
|
+
if (result.status !== 'completed' || !result.file)
|
|
97
|
+
throw new PraxisError({ code: 'INVALID_RESPONSE', message: 'Complete did not return a completed Upload with a file' });
|
|
98
|
+
return await active(result.file);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
work.abort();
|
|
102
|
+
if (uploadId) {
|
|
103
|
+
// Cleanup uses its own timeout and must not inherit the aborted work signal.
|
|
104
|
+
await client.file.uploads.cancel({ path: { upload_id: uploadId }, maxRetries: 0, timeoutMs: 10_000 }).catch(() => { });
|
|
105
|
+
}
|
|
106
|
+
if (options.signal?.aborted)
|
|
107
|
+
throw options.signal.reason;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
options.signal?.removeEventListener('abort', abort);
|
|
112
|
+
}
|
|
113
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,3 +9,5 @@ export { publish } from './helpers/application-publish.js';
|
|
|
9
9
|
export type { PublishInput, PublishResult, AssetsConfig } from './helpers/application-publish.js';
|
|
10
10
|
export { count } from './helpers/database-count.js';
|
|
11
11
|
export type { CountInput, CountResult } from './helpers/database-count.js';
|
|
12
|
+
export { uploadFile } from './helpers/file-upload.js';
|
|
13
|
+
export type { UploadFileOptions, UploadedFile } from './helpers/file-upload.js';
|
package/dist/index.js
CHANGED