@@ -8,10 +8,14 @@ import {
88 HTTPMethod ,
99 Headers ,
1010 CrossPlatformResponse ,
11+ UnifiedFormData ,
12+ FilePathOrFileObject ,
1113} from './types' ;
1214import { AI21EnvConfig } from './EnvConfig' ;
13- import { createFetchInstance } from './runtime ' ;
15+ import { createFetchInstance , createFilesHandlerInstance } from './factory ' ;
1416import { Fetch } from 'fetch' ;
17+ import { BaseFilesHandler } from 'files/BaseFilesHandler' ;
18+ import { FormDataRequest } from 'types/API' ;
1519
1620const validatePositiveInteger = ( name : string , n : unknown ) : number => {
1721 if ( typeof n !== 'number' || ! Number . isInteger ( n ) ) {
@@ -23,42 +27,80 @@ const validatePositiveInteger = (name: string, n: unknown): number => {
2327 return n ;
2428} ;
2529
30+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31+ const appendBodyToFormData = ( formData : UnifiedFormData , body : Record < string , any > ) : void => {
32+ for ( const [ key , value ] of Object . entries ( body ) ) {
33+ if ( Array . isArray ( value ) ) {
34+ value . forEach ( ( item ) => formData . append ( key , item ) ) ;
35+ } else {
36+ formData . append ( key , value ) ;
37+ }
38+ }
39+ } ;
40+
2641export abstract class APIClient {
2742 protected baseURL : string ;
2843 protected maxRetries : number ;
2944 protected timeout : number ;
3045 protected fetch : Fetch ;
46+ protected filesHandler : BaseFilesHandler ;
3147
3248 constructor ( {
3349 baseURL,
3450 maxRetries = AI21EnvConfig . MAX_RETRIES ,
3551 timeout = AI21EnvConfig . TIMEOUT_SECONDS ,
3652 fetch = createFetchInstance ( ) ,
53+ filesHandler = createFilesHandlerInstance ( ) ,
3754 } : {
3855 baseURL : string ;
3956 maxRetries ?: number | undefined ;
4057 timeout : number | undefined ;
4158 fetch ?: Fetch ;
59+ filesHandler ?: BaseFilesHandler ;
4260 } ) {
4361 this . baseURL = baseURL ;
4462 this . maxRetries = validatePositiveInteger ( 'maxRetries' , maxRetries ) ;
4563 this . timeout = validatePositiveInteger ( 'timeout' , timeout ) ;
4664 this . fetch = fetch ;
65+ this . filesHandler = filesHandler ;
4766 }
4867 get < Req , Rsp > ( path : string , opts ?: RequestOptions < Req > ) : Promise < Rsp > {
49- return this . makeRequest ( 'get' , path , opts ) ;
68+ return this . prepareAndExecuteRequest ( 'get' , path , opts ) ;
5069 }
5170
5271 post < Req , Rsp > ( path : string , opts ?: RequestOptions < Req > ) : Promise < Rsp > {
53- return this . makeRequest ( 'post' , path , opts ) ;
72+ return this . prepareAndExecuteRequest ( 'post' , path , opts ) ;
5473 }
5574
5675 put < Req , Rsp > ( path : string , opts ?: RequestOptions < Req > ) : Promise < Rsp > {
57- return this . makeRequest ( 'put' , path , opts ) ;
76+ return this . prepareAndExecuteRequest ( 'put' , path , opts ) ;
5877 }
5978
6079 delete < Req , Rsp > ( path : string , opts ?: RequestOptions < Req > ) : Promise < Rsp > {
61- return this . makeRequest ( 'delete' , path , opts ) ;
80+ return this . prepareAndExecuteRequest ( 'delete' , path , opts ) ;
81+ }
82+
83+ upload < Req , Rsp > ( path : string , file : FilePathOrFileObject , opts ?: RequestOptions < Req > ) : Promise < Rsp > {
84+ return this . filesHandler . prepareFormDataRequest ( file ) . then ( ( formDataRequest : FormDataRequest ) => {
85+ if ( opts ?. body ) {
86+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87+ appendBodyToFormData ( formDataRequest . formData , opts . body as Record < string , any > ) ;
88+ }
89+
90+ const headers = {
91+ ...opts ?. headers ,
92+ ...formDataRequest . headers ,
93+ } ;
94+
95+ const options : FinalRequestOptions = {
96+ method : 'post' ,
97+ path : path ,
98+ body : formDataRequest . formData ,
99+ headers,
100+ } ;
101+
102+ return this . performRequest ( options ) . then ( ( response ) => this . fetch . handleResponse < Rsp > ( response ) as Rsp ) ;
103+ } ) ;
62104 }
63105
64106 protected getUserAgent ( ) : string {
@@ -70,38 +112,56 @@ export abstract class APIClient {
70112 }
71113
72114 protected defaultHeaders ( opts : FinalRequestOptions ) : Headers {
73- return {
115+ const defaultHeaders = {
74116 Accept : 'application/json' ,
75- 'Content-Type' : 'application/json' ,
76117 'User-Agent' : this . getUserAgent ( ) ,
77118 ...this . authHeaders ( opts ) ,
78119 } ;
120+
121+ return { ...defaultHeaders , ...opts . headers } ;
79122 }
80123
81124 // eslint-disable-next-line @typescript-eslint/no-unused-vars
82125 protected authHeaders ( opts : FinalRequestOptions ) : Headers {
83126 return { } ;
84127 }
85128
86- private makeRequest < Req , Rsp > ( method : HTTPMethod , path : string , opts ?: RequestOptions < Req > ) : Promise < Rsp > {
129+ private buildFullUrl ( path : string , query ?: Record < string , unknown > ) : string {
130+ let url = `${ this . baseURL } ${ path } ` ;
131+ if ( query ) {
132+ const queryString = new URLSearchParams ( query as Record < string , string > ) . toString ( ) ;
133+ url += `?${ queryString } ` ;
134+ }
135+ return url ;
136+ }
137+
138+ private prepareAndExecuteRequest < Req , Rsp > (
139+ method : HTTPMethod ,
140+ path : string ,
141+ opts ?: RequestOptions < Req > ,
142+ ) : Promise < Rsp > {
87143 const options = {
88144 method,
89145 path,
90146 ...opts ,
91- } ;
147+ } as FinalRequestOptions ;
92148
93- return this . performRequest ( options as FinalRequestOptions ) . then (
94- ( response ) => this . fetch . handleResponse < Rsp > ( response ) as Rsp ,
95- ) ;
149+ if ( options ?. body ) {
150+ options . body = JSON . stringify ( options . body ) ;
151+ options . headers = { ...options . headers , 'Content-Type' : 'application/json' } ;
152+ }
153+
154+ return this . performRequest ( options ) . then ( ( response ) => this . fetch . handleResponse < Rsp > ( response ) as Rsp ) ;
96155 }
97156
98157 private async performRequest ( options : FinalRequestOptions ) : Promise < APIResponseProps > {
99- const url = ` ${ this . baseURL } ${ options . path } ` ;
158+ const url = this . buildFullUrl ( options . path , options . query as Record < string , unknown > ) ;
100159
101160 const headers = {
102161 ...this . defaultHeaders ( options ) ,
103162 ...options . headers ,
104163 } ;
164+
105165 const response = await this . fetch . call ( url , { ...options , headers } ) ;
106166
107167 if ( ! response . ok ) {
0 commit comments