@@ -16,6 +16,9 @@ const BASE_HEADERS = {
1616const MAX_NB_RETRY = 5 ;
1717const RETRY_DELAY_MS = 200 ;
1818
19+ /**
20+ * Attempts a fetch call multiple times to work around transient network failures.
21+ */
1922async function fetchRetry (
2023 input : RequestInfo | URL ,
2124 init ?: RequestInit | undefined
@@ -39,32 +42,44 @@ async function fetchRetry(
3942 }
4043}
4144
45+ /**
46+ * Simple timeout helper used by the retry loop.
47+ */
4248function sleep ( delay : number ) {
4349 return new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ;
4450}
4551
46- export type apiFetchArgs < B , QP > = {
52+ /**
53+ * Arguments supplied to `apiFetch` describing the HTTP request and validation schemas.
54+ */
55+ type SchemaOrUndefined < Schema extends z . ZodSchema | undefined > =
56+ Schema extends z . ZodSchema ? z . infer < Schema > : undefined ;
57+
58+ export type apiFetchArgs <
59+ RequestSchema extends z . ZodSchema | undefined ,
60+ QueryParamsSchema extends z . ZodSchema | undefined ,
61+ > = {
4762 config : ( ) => RequestConfig ;
4863 path : string ;
49- queryParams ?: QP ;
64+ queryParams ?: SchemaOrUndefined < QueryParamsSchema > ;
5065 request ?: Omit < RequestInit , "headers" | "body" > & { body ?: string } ;
5166 useSession ?: boolean ;
52- bodyRaw ?: B ;
67+ bodyRaw ?: SchemaOrUndefined < RequestSchema > ;
5368} ;
5469
55- type schemaOrUndefined < A > = A extends z . ZodSchema
56- ? z . infer < A >
57- : never | undefined ;
58-
59- export async function apiFetch ( props : {
60- args : apiFetchArgs <
61- schemaOrUndefined < typeof props . requestSchema > ,
62- schemaOrUndefined < typeof props . queryParamsSchema >
63- > ;
64- requestSchema : z . ZodSchema | undefined ;
65- queryParamsSchema : z . ZodSchema | undefined ;
66- responseSchema : z . ZodSchema | undefined ;
67- } ) : Promise < schemaOrUndefined < typeof props . responseSchema > > {
70+ /**
71+ * Validates input payloads, executes the HTTP request, and parses the response with Zod schemas.
72+ */
73+ export async function apiFetch <
74+ RequestSchema extends z . ZodSchema | undefined ,
75+ QueryParamsSchema extends z . ZodSchema | undefined ,
76+ ResponseSchema extends z . ZodSchema | undefined ,
77+ > ( props : {
78+ args : apiFetchArgs < RequestSchema , QueryParamsSchema > ;
79+ requestSchema : RequestSchema ;
80+ queryParamsSchema : QueryParamsSchema ;
81+ responseSchema : ResponseSchema ;
82+ } ) : Promise < SchemaOrUndefined < ResponseSchema > > {
6883 const { args, requestSchema, queryParamsSchema, responseSchema } = props ;
6984
7085 if ( requestSchema && args . bodyRaw ) {
@@ -73,6 +88,7 @@ export async function apiFetch(props: {
7388 throw new RequestBodyParseError ( parsedRequestBody . error ) ;
7489 }
7590 }
91+
7692 if ( queryParamsSchema && args . queryParams ) {
7793 const parsedQueryParams = queryParamsSchema . safeParse ( args . queryParams ) ;
7894 if ( ! parsedQueryParams . success ) {
@@ -81,14 +97,14 @@ export async function apiFetch(props: {
8197 }
8298
8399 const { config, path, request, queryParams, useSession = false } = args ;
100+ const configSnapshot = config ( ) ;
84101 const usedConfig : RequestConfig = useSession
85- ? config ( )
102+ ? configSnapshot
86103 : {
87- apiHost : config ( ) . apiHost ,
104+ apiHost : configSnapshot . apiHost ,
88105 sessionId : undefined ,
89106 } ;
90- // TODO: Query params have stronger types, but they are not just shown here.
91- // Look into furthering the ensuring of passing proper query params.
107+ const sessionWasUsed = Boolean ( usedConfig . sessionId ) ;
92108 const url = getUrl ( usedConfig , path , queryParams ) ;
93109
94110 const response = await fetchRetry ( url , {
@@ -100,19 +116,27 @@ export async function apiFetch(props: {
100116 } ) ;
101117
102118 if ( ! response . ok ) {
103- throw await ApiError . createFromResponse ( response ) ;
119+ const apiError = await ApiError . createFromResponse ( response , {
120+ sessionWasUsed,
121+ } ) ;
122+ throw apiError ;
104123 }
105124
106- if ( responseSchema === undefined ) return undefined ;
125+ if ( responseSchema === undefined ) {
126+ return undefined as SchemaOrUndefined < ResponseSchema > ;
127+ }
107128
108129 const parsed = responseSchema . safeParse ( await response . json ( ) ) ;
109130 if ( ! parsed . success ) {
110131 throw new ParseError ( parsed . error ) ;
111- } else {
112- return parsed . data ;
113132 }
133+
134+ return parsed . data ;
114135}
115136
137+ /**
138+ * Derives authentication headers based on the provided request configuration.
139+ */
116140function getAuthHeaders ( config : RequestConfig ) : RequestInit [ "headers" ] {
117141 return config . sessionId
118142 ? {
@@ -121,6 +145,9 @@ function getAuthHeaders(config: RequestConfig): RequestInit["headers"] {
121145 : { } ;
122146}
123147
148+ /**
149+ * Builds the request URL with optional query parameters.
150+ */
124151function getUrl (
125152 config : RequestConfig ,
126153 path : string ,
0 commit comments