11import { Errors } from "./mod.ts" ;
22
3+ type HeadersPredicate = ( body ?: unknown ) => Record < string , string >
4+
35export interface ApiInitOptions {
4- base ?: string
56 throwOnError : boolean
7+ base ?: string
8+ /**
9+ * Allows to add custom header generation logic based on request body.
10+ * Useful for middleware processing; as body is not necessary for headers parsing
11+ *
12+ * @example
13+ * ```ts
14+ * const api = new ApiFetch().init({
15+ * base: 'https://base_service_com/api',
16+ * throwOnError: true,
17+ * headers: function (body: unknown) {
18+ * const { authorId, correlationId } = body as { authorId: string, correlationId: string }
19+ * return {
20+ * 'X-Author-ID': authorId,
21+ * 'X-Correlation-ID': correlationId
22+ * }
23+ * },
24+ * })
25+ * ```
26+ */
27+ headers ?: HeadersPredicate
628}
729
830export interface ApiGetOptions {
31+ /**
32+ * Request url. Url can be absolute or relative
33+ *
34+ * @example Relative request
35+ * ```ts
36+ * const { user } = api.get<Cqrs.QueryResult>({ url: `/user/${user.id}` })
37+ * ```
38+ *
39+ * @example Absolute request
40+ * ```ts
41+ * const { user } = api.get<Cqrs.QueryResult>({
42+ * url: 'https://www.example.com/api/user/get',
43+ * })
44+ * ```
45+ */
946 url : string
10- headers ?: Record < string , string >
47+ /**
48+ * Headers or HeadersPredicate.
49+ * For headers predicate, @see ApiInitOptions.headers
50+ */
51+ headers ?: Record < string , string > | HeadersPredicate
1152}
1253
1354export interface ApiPostOptions extends ApiGetOptions {
@@ -26,9 +67,9 @@ export class ApiFetch implements Api {
2667 init ( options ?: ApiInitOptions ) : Api {
2768 let base = options ?. base
2869 if ( base && base . endsWith ( '/' ) ) {
29- base = base . substring ( 0 , base . length - 2 )
70+ base = base . substring ( 0 , base . length - 1 )
3071 }
31- this . options = { base, throwOnError : options ?. throwOnError ?? true }
72+ this . options = { base, throwOnError : options ?. throwOnError ?? true , headers : options ?. headers }
3273 return this
3374 }
3475
@@ -46,29 +87,44 @@ export class ApiFetch implements Api {
4687 }
4788
4889 get < R extends object > ( options : ApiGetOptions ) : Promise < R > {
90+ const initOptions = this . options
4991 return new Promise ( async ( resolve , reject ) => {
5092 if ( ! options . url )
5193 throw new Errors . ArgumentError ( 'url' )
5294
5395 const url = this . _url ( options . url )
54- if ( ! options . headers ) {
55- options . headers = { } as Record < string , string >
96+ const headers = { } as Record < string , string >
97+ if ( options . headers ) {
98+ let records = typeof ( options . headers ) === 'function' ? options . headers ( ) : options . headers
99+ if ( records ) {
100+ for ( const [ key , value ] of Object . entries ( records ) ) {
101+ headers [ key ] = value
102+ }
103+ }
104+ }
105+ if ( initOptions ?. headers ) {
106+ const records = initOptions . headers ( )
107+ if ( records ) {
108+ for ( const [ key , value ] of Object . entries ( records ) ) {
109+ headers [ key ] = value
110+ }
111+ }
56112 }
57- options . headers [ 'Accept' ] = 'application/json'
58- options . headers [ 'Content-Type' ] = 'application/json'
113+ headers [ 'Accept' ] = 'application/json'
114+ headers [ 'Content-Type' ] = 'application/json'
59115 try {
60116 const res = await fetch ( url , {
61117 method : 'GET' ,
62- headers : options . headers ,
118+ headers,
63119 } )
64120 const json = await res . json ( )
65121 if ( res . status !== 200 && res . status !== 201 ) {
66122 if ( json . error ?. code ) {
67- throw new Errors . ApiError ( 'POST ' , url , res . status , json . error ?. code , json . error ?. message )
123+ throw new Errors . ApiError ( 'GET ' , url , res . status , json . error ?. code , json . error ?. message )
68124 }
69125 else {
70126 // generic error code
71- throw new Errors . ApiError ( 'POST ' , url , res . status , 'service' , JSON . stringify ( json ) )
127+ throw new Errors . ApiError ( 'GET ' , url , res . status , 'service' , JSON . stringify ( json ) )
72128 }
73129 }
74130 return resolve ( json as R )
@@ -77,30 +133,44 @@ export class ApiFetch implements Api {
77133 if ( error . code )
78134 return reject ( error )
79135 // generic error
80- return reject ( new Errors . ApiError ( 'POST ' , url , 500 , 'service' , error . message ) )
136+ return reject ( new Errors . ApiError ( 'GET ' , url , 500 , 'service' , error . message ) )
81137 }
82138 } )
83139 }
84140
85141 post < R extends object > ( options : ApiPostOptions ) : Promise < R > {
142+ const initOptions = this . options
86143 return new Promise ( async ( resolve , reject ) => {
87144 if ( ! options . url )
88145 throw new Errors . ArgumentError ( 'url' )
89146 if ( ! options . body )
90147 throw new Errors . ArgumentError ( 'body' )
91148
92149 const url = this . _url ( options . url )
93- if ( ! options . headers ) {
94- options . headers = { } as Record < string , string >
150+ const headers = { } as Record < string , string >
151+ if ( options . headers ) {
152+ const records = typeof ( options . headers ) === 'function' ? options . headers ( ) : options . headers ;
153+ if ( records ) {
154+ for ( const [ key , value ] of Object . entries ( records ) ) {
155+ headers [ key ] = value
156+ }
157+ }
158+ }
159+ if ( initOptions ?. headers ) {
160+ const records = initOptions . headers ( options . body )
161+ if ( records ) {
162+ for ( const [ key , value ] of Object . entries ( records ) ) {
163+ headers [ key ] = value
164+ }
165+ }
95166 }
96-
97- options . headers [ 'Accept' ] = 'application/json'
98- options . headers [ 'Content-Type' ] = 'application/json'
167+ headers [ 'Accept' ] = 'application/json'
168+ headers [ 'Content-Type' ] = 'application/json'
99169 try {
100170 const res = await fetch ( url , {
101171 method : 'POST' ,
102172 body : JSON . stringify ( options . body ) ,
103- headers : options . headers ,
173+ headers,
104174 } )
105175 const json = await res . json ( )
106176 if ( res . status !== 200 && res . status !== 201 ) {
0 commit comments