1- import { ApiMethod , NO_CONTENT } from '@react-enhanced/shared'
1+ import { ApiMethod } from '@react-enhanced/shared'
22import type { Nilable , URLSearchParamsOptions } from '@react-enhanced/types'
33import {
44 CONTENT_TYPE ,
88import { isPlainObject } from 'lodash'
99import { useCallback , useState } from 'react'
1010import type { Observable } from 'rxjs'
11- import { NEVER , catchError , from , of , switchMap , tap , throwError } from 'rxjs'
11+ import { NEVER , from , of , switchMap , tap } from 'rxjs'
1212import { fromFetch } from 'rxjs/fetch'
1313
1414import { useEnhancedEffect } from './lifecycle.js'
@@ -19,53 +19,45 @@ export interface FetchApiOptions extends Omit<RequestInit, 'body' | 'method'> {
1919 query ?: URLSearchParamsOptions
2020 json ?: boolean
2121 type ?: 'arrayBuffer' | 'blob' | 'json' | 'text' | null
22- mock ?: boolean
2322}
2423
2524export interface InterceptorRequest extends FetchApiOptions {
2625 url : string
2726}
2827
29- export type RequestInterceptor = (
28+ export type ApiInterceptor = (
3029 request : InterceptorRequest ,
31- ) =>
32- | InterceptorRequest
33- | Observable < InterceptorRequest >
34- | PromiseLike < InterceptorRequest >
35-
36- export type ResponseInterceptor = (
37- request : InterceptorRequest ,
38- response : Response ,
30+ next : ( request : InterceptorRequest ) => Observable < Response > ,
3931) => Observable < Response > | PromiseLike < Response > | Response
4032
4133export type ResponseError < T extends api . Error = api . Error > = T & {
4234 data ?: T | null
4335 response ?: Response | null
4436}
4537
46- export type ErrorInterceptor < T extends api . Error = api . Error > = (
47- request : InterceptorRequest ,
48- error : ResponseError < T > ,
49- ) => Observable < Response > | PromiseLike < Response > | Response
38+ export class ApiInterceptors {
39+ readonly #interceptors: ApiInterceptor [ ] = [ ]
5040
51- export interface ApiInterceptors {
52- request : {
53- end ( ) : ApiInterceptors
54- use ( interceptor : RequestInterceptor ) : ApiInterceptors [ 'request' ]
55- eject ( interceptor : RequestInterceptor ) : boolean
41+ get length ( ) {
42+ return this . #interceptors. length
5643 }
57- response : {
58- end ( ) : ApiInterceptors
59- use ( interceptor : ResponseInterceptor ) : ApiInterceptors [ 'response' ]
60- use < T extends api . Error = api . Error > (
61- responseInterceptor : ResponseInterceptor | null ,
62- errorInterceptor : ErrorInterceptor < T > ,
63- ) : ApiInterceptors [ 'response' ]
64- eject ( interceptor : ResponseInterceptor ) : boolean
65- eject < T extends api . Error = api . Error > (
66- responseInterceptor : ResponseInterceptor | null ,
67- errorInterceptor : ErrorInterceptor < T > ,
68- ) : boolean
44+
45+ at ( index : number ) : ApiInterceptor | undefined {
46+ return this . #interceptors. at ( index )
47+ }
48+
49+ use ( ...interceptors : ApiInterceptor [ ] ) : ApiInterceptors {
50+ this . #interceptors. push ( ...interceptors )
51+ return this
52+ }
53+
54+ eject ( interceptor : ApiInterceptor ) : boolean {
55+ const index = this . #interceptors. indexOf ( interceptor )
56+ if ( index > - 1 ) {
57+ this . #interceptors. splice ( index , 1 )
58+ return true
59+ }
60+ return false
6961 }
7062}
7163
@@ -90,151 +82,8 @@ export interface UseApiOptions extends FetchApiOptions {
9082 url ?: string
9183}
9284
93- const createInterceptors = ( ) => {
94- const requestInterceptors = new Set < RequestInterceptor > ( )
95- const responseInterceptors = new Set < ResponseInterceptor > ( )
96- const errorInterceptors = new Set < ErrorInterceptor > ( )
97-
98- const interceptors : ApiInterceptors = {
99- request : {
100- end ( ) {
101- return interceptors
102- } ,
103- use ( interceptor : RequestInterceptor ) {
104- requestInterceptors . add ( interceptor )
105- return interceptors . request
106- } ,
107- eject ( interceptor : RequestInterceptor ) {
108- return requestInterceptors . delete ( interceptor )
109- } ,
110- } ,
111- response : {
112- end ( ) {
113- return interceptors
114- } ,
115- use (
116- responseInterceptor : ResponseInterceptor | null ,
117- errorInterceptor ?: ErrorInterceptor ,
118- ) {
119- if ( responseInterceptor ) {
120- responseInterceptors . add ( responseInterceptor )
121- }
122-
123- if ( errorInterceptor ) {
124- errorInterceptors . add ( errorInterceptor )
125- }
126-
127- return interceptors . response
128- } ,
129- eject (
130- responseInterceptor : ResponseInterceptor | null ,
131- errorInterceptor ?: ErrorInterceptor ,
132- ) {
133- if ( ! responseInterceptor && ! errorInterceptor ) {
134- return false
135- }
136- const resIcDeleted =
137- ! responseInterceptor ||
138- responseInterceptors . delete ( responseInterceptor )
139- const errIcDeleted =
140- ! errorInterceptor || errorInterceptors . delete ( errorInterceptor )
141- return resIcDeleted && errIcDeleted
142- } ,
143- } ,
144- }
145-
146- return {
147- interceptors,
148- requestInterceptors,
149- responseInterceptors,
150- errorInterceptors,
151- }
152- }
153-
154- const invokeRequestInterceptors = (
155- requestInterceptors : Set < RequestInterceptor > ,
156- req : InterceptorRequest ,
157- ) =>
158- [ ...requestInterceptors ] . reduce (
159- ( acc , interceptor ) =>
160- acc . pipe (
161- switchMap ( req => {
162- const next = interceptor ( req )
163- return isObservableLike ( next ) ? next : of ( next )
164- } ) ,
165- ) ,
166- of ( req ) ,
167- )
168-
169- const invokeResponseInterceptors = < T > (
170- responseInterceptors : Set < ResponseInterceptor > ,
171- req : InterceptorRequest ,
172- res : Response ,
173- type : FetchApiOptions [ 'type' ] ,
174- ) =>
175- [ ...responseInterceptors ]
176- . reduce (
177- ( acc , interceptor ) =>
178- acc . pipe (
179- switchMap ( res => {
180- const next = interceptor ( req , res )
181- return isObservableLike ( next ) ? next : of ( next )
182- } ) ,
183- ) ,
184- of ( res ) ,
185- )
186- . pipe (
187- switchMap ( res =>
188- from (
189- res . status === NO_CONTENT
190- ? of ( null )
191- : type == null
192- ? of ( res )
193- : ( res . clone ( ) [ type ] ( ) as Promise < T > ) ,
194- ) . pipe (
195- catchError ( ( err : Error ) =>
196- throwError ( ( ) =>
197- Object . assign ( new Error ( err . message ) , { response : res } ) ,
198- ) ,
199- ) ,
200- switchMap ( data => {
201- if ( res . ok ) {
202- return of ( data )
203- }
204- return throwError ( ( ) =>
205- Object . assign ( new Error ( res . statusText ) , {
206- data,
207- response : res ,
208- } ) ,
209- )
210- } ) ,
211- ) ,
212- ) ,
213- )
214-
215- const invokeErrorInterceptors = (
216- errorInterceptors : Set < ErrorInterceptor > ,
217- req : InterceptorRequest ,
218- err : ResponseError ,
219- ) =>
220- [ ...errorInterceptors ] . reduce < Observable < Response > > (
221- ( acc , interceptor ) =>
222- acc . pipe (
223- catchError ( ( err : ResponseError ) => {
224- const next = interceptor ( req , err )
225- return isObservableLike ( next ) ? next : of ( next )
226- } ) ,
227- ) ,
228- throwError ( ( ) => err ) ,
229- )
230-
23185export function createApi ( ) {
232- const {
233- interceptors,
234- requestInterceptors,
235- responseInterceptors,
236- errorInterceptors,
237- } = createInterceptors ( )
86+ const interceptors = new ApiInterceptors ( )
23887
23988 function fetchApi (
24089 url : string ,
@@ -273,44 +122,34 @@ export function createApi() {
273122 headers . append ( CONTENT_TYPE , 'application/json' )
274123 }
275124
276- const req : InterceptorRequest = {
125+ let index = 0
126+
127+ const next = ( req : InterceptorRequest ) => {
128+ if ( index < interceptors . length ) {
129+ const res = interceptors . at ( index ++ ) ! ( req , next )
130+ return isObservableLike ( res ) ? from ( res ) : of ( res )
131+ }
132+ const { body, url, query, ...rest } = req
133+ return fromFetch < Response > ( normalizeUrl ( url , query ) , {
134+ ...rest ,
135+ body : json ? JSON . stringify ( body ) : ( body as BodyInit ) ,
136+ selector : res => of ( res ) ,
137+ } )
138+ }
139+
140+ return next ( {
277141 url,
278142 method,
279143 body,
280144 headers,
281145 ...rest ,
282- }
283-
284- return invokeRequestInterceptors ( requestInterceptors , req ) . pipe (
285- switchMap ( req => {
286- const { body, url, query, ...rest } = req
287- return fromFetch < Response > ( normalizeUrl ( url , query ) , {
288- ...rest ,
289- body : json ? JSON . stringify ( body ) : ( body as BodyInit ) ,
290- selector : res => of ( res ) ,
291- } ) . pipe (
292- catchError ( ( err : api . Error ) =>
293- invokeErrorInterceptors ( errorInterceptors , req , err ) ,
294- ) ,
295- switchMap ( res =>
296- invokeResponseInterceptors ( responseInterceptors , req , res , type ) ,
297- ) ,
298- )
146+ } ) . pipe (
147+ switchMap ( res => {
148+ if ( type == null ) {
149+ return of ( res )
150+ }
151+ return res [ type ] ( ) as Promise < T >
299152 } ) ,
300- catchError ( ( err : ResponseError ) =>
301- invokeErrorInterceptors ( errorInterceptors , req , err ) . pipe (
302- switchMap ( res => {
303- if ( type == null ) {
304- return of ( res )
305- }
306- return from ( res . clone ( ) [ type ] ( ) as Promise < T > ) . pipe (
307- catchError ( ( err : Error ) =>
308- throwError ( ( ) => Object . assign ( err , { response : res } ) ) ,
309- ) ,
310- )
311- } ) ,
312- ) ,
313- ) ,
314153 )
315154 }
316155
0 commit comments