@zayne-labs/callapi 0.0.2 → 0.0.4
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/package.json +2 -2
- package/src/createFetchClient.ts +224 -0
- package/src/index.ts +5 -0
- package/src/type-helpers.ts +8 -0
- package/src/types.ts +146 -0
- package/src/utils.ts +251 -0
- package/dist/createFetchClient.cjs +0 -9
- package/dist/createFetchClient.cjs.map +0 -1
- package/dist/createFetchClient.d.cts +0 -21
- package/dist/createFetchClient.d.ts +0 -21
- package/dist/createFetchClient.js +0 -7
- package/dist/createFetchClient.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -1
- package/dist/types-Cf2V09St.d.cts +0 -147
- package/dist/types-Cf2V09St.d.ts +0 -147
- package/dist/utils.cjs +0 -23
- package/dist/utils.cjs.map +0 -1
- package/dist/utils.d.cts +0 -1
- package/dist/utils.d.ts +0 -1
- package/dist/utils.js +0 -5
- package/dist/utils.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zayne-labs/callapi",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A lightweight wrapper over fetch with quality of life improvements like built-in request cancellation, retries, interceptors and more",
|
|
6
6
|
"repository": "ryan-zayne/callApi",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
|
-
"
|
|
32
|
+
"src"
|
|
33
33
|
],
|
|
34
34
|
"sideEffects": false,
|
|
35
35
|
"devDependencies": {
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
$RequestConfig,
|
|
3
|
+
AbortSignalWithAny,
|
|
4
|
+
BaseConfig,
|
|
5
|
+
ExtraOptions,
|
|
6
|
+
FetchConfig,
|
|
7
|
+
GetCallApiResult,
|
|
8
|
+
PossibleErrorObject,
|
|
9
|
+
ResultStyleUnion,
|
|
10
|
+
} from "./types";
|
|
11
|
+
import {
|
|
12
|
+
$resolveErrorResult,
|
|
13
|
+
HTTPError,
|
|
14
|
+
defaultRetryCodes,
|
|
15
|
+
defaultRetryMethods,
|
|
16
|
+
getResponseData,
|
|
17
|
+
isFormData,
|
|
18
|
+
isHTTPErrorInfo,
|
|
19
|
+
isHTTPErrorInstance,
|
|
20
|
+
isObject,
|
|
21
|
+
mergeUrlWithParams,
|
|
22
|
+
objectifyHeaders,
|
|
23
|
+
resolveSuccessResult,
|
|
24
|
+
splitConfig,
|
|
25
|
+
wait,
|
|
26
|
+
} from "./utils";
|
|
27
|
+
|
|
28
|
+
const createFetchClient = <
|
|
29
|
+
TBaseData,
|
|
30
|
+
TBaseErrorData,
|
|
31
|
+
TBaseResultMode extends ResultStyleUnion = undefined,
|
|
32
|
+
>(
|
|
33
|
+
baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>
|
|
34
|
+
) => {
|
|
35
|
+
const abortControllerStore = new Map<string, AbortController>();
|
|
36
|
+
|
|
37
|
+
const [baseFetchConfig, baseExtraOptions] = splitConfig(baseConfig ?? {});
|
|
38
|
+
|
|
39
|
+
const { headers: baseHeaders, signal: baseSignal, ...restOfBaseFetchConfig } = baseFetchConfig;
|
|
40
|
+
|
|
41
|
+
const callApi = async <
|
|
42
|
+
TData = TBaseData,
|
|
43
|
+
TErrorData = TBaseErrorData,
|
|
44
|
+
TResultMode extends ResultStyleUnion = TBaseResultMode,
|
|
45
|
+
>(
|
|
46
|
+
url: string,
|
|
47
|
+
config?: FetchConfig<TData, TErrorData, TResultMode>
|
|
48
|
+
): Promise<GetCallApiResult<TData, TErrorData, TResultMode>> => {
|
|
49
|
+
type CallApiResult = GetCallApiResult<TData, TErrorData, TResultMode>;
|
|
50
|
+
|
|
51
|
+
const [fetchConfig, extraOptions] = splitConfig(config ?? {});
|
|
52
|
+
|
|
53
|
+
const options = {
|
|
54
|
+
bodySerializer: JSON.stringify,
|
|
55
|
+
responseType: "json",
|
|
56
|
+
baseURL: "",
|
|
57
|
+
retries: 0,
|
|
58
|
+
retryDelay: 0,
|
|
59
|
+
retryCodes: defaultRetryCodes,
|
|
60
|
+
retryMethods: defaultRetryMethods,
|
|
61
|
+
defaultErrorMessage: "Failed to fetch data from server!",
|
|
62
|
+
cancelRedundantRequests: true,
|
|
63
|
+
...baseExtraOptions,
|
|
64
|
+
...extraOptions,
|
|
65
|
+
} satisfies ExtraOptions;
|
|
66
|
+
|
|
67
|
+
const { signal = baseSignal, body, headers, ...restOfFetchConfig } = fetchConfig;
|
|
68
|
+
|
|
69
|
+
const prevFetchController = abortControllerStore.get(url);
|
|
70
|
+
|
|
71
|
+
if (prevFetchController && options.cancelRedundantRequests) {
|
|
72
|
+
const reason = new DOMException("Cancelled the previous unfinished request", "AbortError");
|
|
73
|
+
prevFetchController.abort(reason);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const newFetchController = new AbortController();
|
|
77
|
+
|
|
78
|
+
abortControllerStore.set(url, newFetchController);
|
|
79
|
+
|
|
80
|
+
const timeoutSignal = options.timeout ? AbortSignal.timeout(options.timeout) : null;
|
|
81
|
+
|
|
82
|
+
// FIXME - Remove this type cast once TS updates its lib-dom types for AbortSignal to include the any() method
|
|
83
|
+
const combinedSignal = (AbortSignal as AbortSignalWithAny).any([
|
|
84
|
+
newFetchController.signal,
|
|
85
|
+
timeoutSignal ?? newFetchController.signal,
|
|
86
|
+
signal ?? newFetchController.signal,
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const requestInit = {
|
|
90
|
+
signal: combinedSignal,
|
|
91
|
+
|
|
92
|
+
method: "GET",
|
|
93
|
+
|
|
94
|
+
body: isObject(body) ? options.bodySerializer(body) : body,
|
|
95
|
+
|
|
96
|
+
// == Return undefined if there are no headers provided or if the body is not an object
|
|
97
|
+
headers:
|
|
98
|
+
baseHeaders || headers || isObject(body)
|
|
99
|
+
? {
|
|
100
|
+
...(isObject(body) && {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
Accept: "application/json",
|
|
103
|
+
}),
|
|
104
|
+
...(isFormData(body) && {
|
|
105
|
+
"Content-Type": "multipart/form-data",
|
|
106
|
+
}),
|
|
107
|
+
...objectifyHeaders(baseHeaders),
|
|
108
|
+
...objectifyHeaders(headers),
|
|
109
|
+
}
|
|
110
|
+
: undefined,
|
|
111
|
+
|
|
112
|
+
...restOfBaseFetchConfig,
|
|
113
|
+
...restOfFetchConfig,
|
|
114
|
+
} satisfies $RequestConfig;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await options.onRequest?.({ request: requestInit, options });
|
|
118
|
+
|
|
119
|
+
const response = await fetch(
|
|
120
|
+
`${options.baseURL}${mergeUrlWithParams(url, options.query)}`,
|
|
121
|
+
requestInit
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const shouldRetry =
|
|
125
|
+
!combinedSignal.aborted &&
|
|
126
|
+
options.retries !== 0 &&
|
|
127
|
+
!response.ok &&
|
|
128
|
+
options.retryCodes.includes(response.status) &&
|
|
129
|
+
options.retryMethods.includes(requestInit.method);
|
|
130
|
+
|
|
131
|
+
if (shouldRetry) {
|
|
132
|
+
await wait(options.retryDelay);
|
|
133
|
+
|
|
134
|
+
return await callApi(url, { ...config, retries: options.retries - 1 });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
const errorData = await getResponseData<TErrorData>(
|
|
139
|
+
response,
|
|
140
|
+
options.responseType,
|
|
141
|
+
options.responseParser
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// == Pushing all error handling responsibilities to the catch block
|
|
145
|
+
throw new HTTPError({
|
|
146
|
+
response: { ...response, errorData },
|
|
147
|
+
defaultErrorMessage: options.defaultErrorMessage,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const successData = await getResponseData<TData>(
|
|
152
|
+
response,
|
|
153
|
+
options.responseType,
|
|
154
|
+
options.responseParser
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
await options.onResponse?.({
|
|
158
|
+
response: { ...response, data: successData },
|
|
159
|
+
request: requestInit,
|
|
160
|
+
options,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return resolveSuccessResult<CallApiResult>({ successData, response, options });
|
|
164
|
+
|
|
165
|
+
// == Exhaustive Error handling
|
|
166
|
+
} catch (error) {
|
|
167
|
+
const resolveErrorResult = $resolveErrorResult<CallApiResult>({ error, options });
|
|
168
|
+
|
|
169
|
+
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
170
|
+
const message = `Request timed out after ${options.timeout}ms`;
|
|
171
|
+
|
|
172
|
+
console.info(`%cTimeoutError: ${message}`, "color: red; font-weight: 500; font-size: 14px;");
|
|
173
|
+
console.trace("TimeoutError");
|
|
174
|
+
|
|
175
|
+
return resolveErrorResult({ message });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
179
|
+
const message = `Request was cancelled`;
|
|
180
|
+
|
|
181
|
+
console.info(`%AbortError: ${message}`, "color: red; font-weight: 500; font-size: 14px;");
|
|
182
|
+
console.trace("AbortError");
|
|
183
|
+
|
|
184
|
+
return resolveErrorResult({ message });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (isHTTPErrorInstance<TErrorData>(error)) {
|
|
188
|
+
const { errorData, ...response } = error.response;
|
|
189
|
+
|
|
190
|
+
await options.onResponseError?.({
|
|
191
|
+
response: { ...response, errorData },
|
|
192
|
+
request: requestInit,
|
|
193
|
+
options,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return resolveErrorResult({
|
|
197
|
+
errorData,
|
|
198
|
+
response,
|
|
199
|
+
message: (errorData as PossibleErrorObject)?.message,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// == At this point only the request errors exist, so the request error interceptor is called
|
|
204
|
+
await options.onRequestError?.({ request: requestInit, error: error as Error, options });
|
|
205
|
+
|
|
206
|
+
return resolveErrorResult();
|
|
207
|
+
|
|
208
|
+
// == Removing the now unneeded AbortController from store
|
|
209
|
+
} finally {
|
|
210
|
+
abortControllerStore.delete(url);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
callApi.create = createFetchClient;
|
|
215
|
+
callApi.isHTTPErrorInfo = isHTTPErrorInfo;
|
|
216
|
+
callApi.isHTTPErrorInstance = isHTTPErrorInstance;
|
|
217
|
+
callApi.cancel = (url: string) => abortControllerStore.get(url)?.abort();
|
|
218
|
+
|
|
219
|
+
return callApi;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const callApi = createFetchClient();
|
|
223
|
+
|
|
224
|
+
export default callApi;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// == These two types allows for adding arbitrary literal types, while still provided autocomplete for defaults.
|
|
2
|
+
// == Usually intersection with "{}" or "NonNullable<unknown>" would make it work fine, but the placeholder with never type is added to make the AnyWhatever type appear last in a given union.
|
|
3
|
+
export type AnyString = string & { placeholder?: never };
|
|
4
|
+
export type AnyNumber = number & { placeholder?: never };
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
+
// == `Any` is required here so that one can pass custom function type without type errors
|
|
8
|
+
export type AnyFunction = (...args: any[]) => any;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/consistent-type-definitions */
|
|
2
|
+
import type { AnyNumber, AnyString } from "./type-helpers";
|
|
3
|
+
import type { HTTPError, fetchSpecificKeys, handleResponseType } from "./utils";
|
|
4
|
+
|
|
5
|
+
export type $RequestConfig = Pick<FetchConfig, (typeof fetchSpecificKeys)[number]>;
|
|
6
|
+
export type $BaseRequestConfig = Omit<$RequestConfig, "body">;
|
|
7
|
+
|
|
8
|
+
export type ExtraOptions<
|
|
9
|
+
TBaseData = unknown,
|
|
10
|
+
TBaseErrorData = unknown,
|
|
11
|
+
TBaseResultMode extends ResultStyleUnion = ResultStyleUnion,
|
|
12
|
+
> = {
|
|
13
|
+
body?: Record<string, unknown> | RequestInit["body"];
|
|
14
|
+
|
|
15
|
+
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE" | AnyString;
|
|
16
|
+
|
|
17
|
+
query?: Record<string, string | number | boolean>;
|
|
18
|
+
bodySerializer?: (bodyData: Record<string, unknown>) => string;
|
|
19
|
+
|
|
20
|
+
responseParser?: {
|
|
21
|
+
(data: string): unknown;
|
|
22
|
+
<TData>(data: string): TData;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
resultMode?: TBaseResultMode;
|
|
26
|
+
|
|
27
|
+
cancelRedundantRequests?: boolean;
|
|
28
|
+
|
|
29
|
+
baseURL?: string;
|
|
30
|
+
|
|
31
|
+
timeout?: number;
|
|
32
|
+
|
|
33
|
+
defaultErrorMessage?: string;
|
|
34
|
+
|
|
35
|
+
throwOnError?: boolean | ((error?: Error | HTTPError<TBaseErrorData>) => boolean);
|
|
36
|
+
|
|
37
|
+
responseType?: keyof ReturnType<typeof handleResponseType>;
|
|
38
|
+
|
|
39
|
+
retries?: number;
|
|
40
|
+
|
|
41
|
+
retryDelay?: number;
|
|
42
|
+
|
|
43
|
+
retryCodes?: Array<409 | 425 | 429 | 500 | 502 | 503 | 504 | AnyNumber>;
|
|
44
|
+
|
|
45
|
+
retryMethods?: Array<"GET" | "POST" | "PATCH" | "DELETE" | AnyString>;
|
|
46
|
+
|
|
47
|
+
meta?: Record<string, unknown>;
|
|
48
|
+
|
|
49
|
+
onRequest?: (requestContext: {
|
|
50
|
+
request: $RequestConfig;
|
|
51
|
+
options: ExtraOptions;
|
|
52
|
+
}) => void | Promise<void>;
|
|
53
|
+
|
|
54
|
+
onRequestError?: (requestContext: {
|
|
55
|
+
request: $RequestConfig;
|
|
56
|
+
error: Error;
|
|
57
|
+
options: ExtraOptions;
|
|
58
|
+
}) => void | Promise<void>;
|
|
59
|
+
|
|
60
|
+
onResponse?: (successContext: {
|
|
61
|
+
request: $RequestConfig;
|
|
62
|
+
response: Response & { data: TBaseData };
|
|
63
|
+
options: ExtraOptions;
|
|
64
|
+
}) => void | Promise<void>;
|
|
65
|
+
|
|
66
|
+
onResponseError?: (errorContext: {
|
|
67
|
+
request: $RequestConfig;
|
|
68
|
+
response: Response & { errorData: TBaseErrorData };
|
|
69
|
+
options: ExtraOptions;
|
|
70
|
+
}) => void | Promise<void>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// prettier-ignore
|
|
74
|
+
export interface FetchConfig<
|
|
75
|
+
TData = unknown,
|
|
76
|
+
TErrorData = unknown,
|
|
77
|
+
TResultMode extends ResultStyleUnion = undefined,
|
|
78
|
+
> extends Omit<RequestInit, "method" | "body">, ExtraOptions<TData, TErrorData, TResultMode> {}
|
|
79
|
+
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
81
|
+
export interface BaseConfig<
|
|
82
|
+
TBaseData = unknown,
|
|
83
|
+
TBaseErrorData = unknown,
|
|
84
|
+
TBaseResultMode extends ResultStyleUnion = undefined,
|
|
85
|
+
> extends Omit<FetchConfig<TBaseData, TBaseErrorData, TBaseResultMode>, "body"> {}
|
|
86
|
+
|
|
87
|
+
type ApiSuccessVariant<TData> = {
|
|
88
|
+
dataInfo: TData;
|
|
89
|
+
errorInfo: null;
|
|
90
|
+
response: Response;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type PossibleErrors =
|
|
94
|
+
| "AbortError"
|
|
95
|
+
| "TimeoutError"
|
|
96
|
+
| "SyntaxError"
|
|
97
|
+
| "TypeError"
|
|
98
|
+
| "Error"
|
|
99
|
+
| "UnknownError";
|
|
100
|
+
|
|
101
|
+
export type ApiErrorVariant<TErrorData> =
|
|
102
|
+
| {
|
|
103
|
+
dataInfo: null;
|
|
104
|
+
errorInfo: {
|
|
105
|
+
errorName: "HTTPError";
|
|
106
|
+
errorData: TErrorData;
|
|
107
|
+
message: string;
|
|
108
|
+
};
|
|
109
|
+
response: Response;
|
|
110
|
+
}
|
|
111
|
+
| {
|
|
112
|
+
dataInfo: null;
|
|
113
|
+
errorInfo: {
|
|
114
|
+
errorName: PossibleErrors;
|
|
115
|
+
message: string;
|
|
116
|
+
};
|
|
117
|
+
response: null;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
type ResultStyleMap<TData = unknown, TErrorData = unknown> = {
|
|
121
|
+
all: ApiSuccessVariant<TData> | ApiErrorVariant<TErrorData>;
|
|
122
|
+
onlySuccess: TData;
|
|
123
|
+
onlyError: TErrorData;
|
|
124
|
+
onlyResponse: Response;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// == Using this double Immediately Indexed Mapped type to get a union of the keys of the object while still showing the full type signature on hover
|
|
128
|
+
export type ResultStyleUnion = {
|
|
129
|
+
_: { [Key in keyof ResultStyleMap]: Key }[keyof ResultStyleMap] | undefined;
|
|
130
|
+
}["_"];
|
|
131
|
+
|
|
132
|
+
export type GetCallApiResult<TData, TErrorData, TResultMode> =
|
|
133
|
+
TResultMode extends NonNullable<ResultStyleUnion>
|
|
134
|
+
? ResultStyleMap<TData, TErrorData>[TResultMode]
|
|
135
|
+
: ResultStyleMap<TData, TErrorData>["all"];
|
|
136
|
+
|
|
137
|
+
export type AbortSignalWithAny = typeof AbortSignal & {
|
|
138
|
+
any: (signalArray: AbortSignal[]) => AbortSignal;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export type PossibleErrorObject =
|
|
142
|
+
| {
|
|
143
|
+
name?: PossibleErrors;
|
|
144
|
+
message?: string;
|
|
145
|
+
}
|
|
146
|
+
| undefined;
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { AnyFunction } from "./type-helpers";
|
|
2
|
+
import type {
|
|
3
|
+
$BaseRequestConfig,
|
|
4
|
+
$RequestConfig,
|
|
5
|
+
BaseConfig,
|
|
6
|
+
ExtraOptions,
|
|
7
|
+
FetchConfig,
|
|
8
|
+
PossibleErrorObject,
|
|
9
|
+
} from "./types";
|
|
10
|
+
|
|
11
|
+
export const mergeUrlWithParams = (url: string, params: ExtraOptions["query"]): string => {
|
|
12
|
+
if (!params) {
|
|
13
|
+
return url;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const paramsString = new URLSearchParams(params as Record<string, string>).toString();
|
|
17
|
+
|
|
18
|
+
if (url.includes("?") && !url.endsWith("?")) {
|
|
19
|
+
return `${url}&${paramsString}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (url.endsWith("?")) {
|
|
23
|
+
return `${url}${paramsString}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return `${url}?${paramsString}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const objectifyHeaders = (headers: RequestInit["headers"]): Record<string, string> | undefined => {
|
|
30
|
+
if (!headers || isObject(headers)) {
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Object.fromEntries(isArray(headers) ? headers : headers.entries());
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const retryCodesLookup = {
|
|
38
|
+
408: "Request Timeout",
|
|
39
|
+
409: "Conflict",
|
|
40
|
+
425: "Too Early",
|
|
41
|
+
429: "Too Many Requests",
|
|
42
|
+
500: "Internal Server Error",
|
|
43
|
+
502: "Bad Gateway",
|
|
44
|
+
503: "Service Unavailable",
|
|
45
|
+
504: "Gateway Timeout",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const defaultRetryCodes: Required<BaseConfig>["retryCodes"] =
|
|
49
|
+
Object.keys(retryCodesLookup).map(Number);
|
|
50
|
+
|
|
51
|
+
export const defaultRetryMethods: Required<BaseConfig>["retryMethods"] = ["GET"];
|
|
52
|
+
|
|
53
|
+
export const fetchSpecificKeys = [
|
|
54
|
+
"body",
|
|
55
|
+
"integrity",
|
|
56
|
+
"method",
|
|
57
|
+
"headers",
|
|
58
|
+
"signal",
|
|
59
|
+
"cache",
|
|
60
|
+
"redirect",
|
|
61
|
+
"window",
|
|
62
|
+
"credentials",
|
|
63
|
+
"keepalive",
|
|
64
|
+
"referrer",
|
|
65
|
+
"priority",
|
|
66
|
+
"mode",
|
|
67
|
+
"referrerPolicy",
|
|
68
|
+
] satisfies Array<keyof FetchConfig>;
|
|
69
|
+
|
|
70
|
+
const omitKeys = <TObject extends Record<string, unknown>, const TOmitArray extends Array<keyof TObject>>(
|
|
71
|
+
initialObject: TObject,
|
|
72
|
+
keysToOmit: TOmitArray
|
|
73
|
+
) => {
|
|
74
|
+
const arrayFromFilteredObject = Object.entries(initialObject).filter(
|
|
75
|
+
([key]) => !keysToOmit.includes(key)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const updatedObject = Object.fromEntries(arrayFromFilteredObject);
|
|
79
|
+
|
|
80
|
+
return updatedObject as Omit<TObject, keyof TOmitArray>;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const pickKeys = <TObject extends Record<string, unknown>, const TPickArray extends Array<keyof TObject>>(
|
|
84
|
+
initialObject: TObject,
|
|
85
|
+
keysToPick: TPickArray
|
|
86
|
+
) => {
|
|
87
|
+
const keysToPickSet = new Set(keysToPick);
|
|
88
|
+
|
|
89
|
+
const arrayFromInitObject = Object.entries(initialObject);
|
|
90
|
+
|
|
91
|
+
const filteredArray = arrayFromInitObject.filter(([objectKey]) => keysToPickSet.has(objectKey));
|
|
92
|
+
|
|
93
|
+
const updatedObject = Object.fromEntries(filteredArray);
|
|
94
|
+
|
|
95
|
+
return updatedObject as Pick<TObject, TPickArray[number]>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const splitConfig = <TObject extends object>(
|
|
99
|
+
config: TObject
|
|
100
|
+
): ["body" extends keyof TObject ? $RequestConfig : $BaseRequestConfig, ExtraOptions] => [
|
|
101
|
+
pickKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,
|
|
102
|
+
omitKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
export const handleResponseType = <TResponse>(
|
|
106
|
+
response: Response,
|
|
107
|
+
parser?: Required<ExtraOptions>["responseParser"]
|
|
108
|
+
) => ({
|
|
109
|
+
json: async () => {
|
|
110
|
+
if (parser) {
|
|
111
|
+
return parser<TResponse>(await response.text());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return response.json() as Promise<TResponse>;
|
|
115
|
+
},
|
|
116
|
+
arrayBuffer: () => response.arrayBuffer() as Promise<TResponse>,
|
|
117
|
+
blob: () => response.blob() as Promise<TResponse>,
|
|
118
|
+
formData: () => response.formData() as Promise<TResponse>,
|
|
119
|
+
text: () => response.text() as Promise<TResponse>,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export const getResponseData = <TResponse>(
|
|
123
|
+
response: Response,
|
|
124
|
+
responseType: keyof ReturnType<typeof handleResponseType>,
|
|
125
|
+
parser: ExtraOptions["responseParser"]
|
|
126
|
+
) => {
|
|
127
|
+
const RESPONSE_TYPE_LOOKUP = handleResponseType<TResponse>(response, parser);
|
|
128
|
+
|
|
129
|
+
if (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, responseType)) {
|
|
130
|
+
throw new Error(`Invalid response type: ${responseType}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return RESPONSE_TYPE_LOOKUP[responseType]();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type DataInfo = {
|
|
137
|
+
successData: unknown;
|
|
138
|
+
options: ExtraOptions;
|
|
139
|
+
response: Response;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// == The CallApiResult type is used to cast all return statements due to a design limitation in ts.
|
|
143
|
+
// LINK - See https://www.zhenghao.io/posts/type-functions for more info
|
|
144
|
+
export const resolveSuccessResult = <CallApiResult>(info: DataInfo): CallApiResult => {
|
|
145
|
+
const { options, response, successData } = info;
|
|
146
|
+
|
|
147
|
+
const apiDetails = { dataInfo: successData, errorInfo: null, response };
|
|
148
|
+
|
|
149
|
+
if (options.resultMode === undefined || options.resultMode === "all") {
|
|
150
|
+
return apiDetails as CallApiResult;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
onlySuccess: apiDetails.dataInfo,
|
|
155
|
+
onlyError: apiDetails.errorInfo,
|
|
156
|
+
onlyResponse: apiDetails.response,
|
|
157
|
+
}[options.resultMode] as CallApiResult;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// == Using curring here so error and options are not required to be passed every time, instead to be captured once by way of closure
|
|
161
|
+
export const $resolveErrorResult = <CallApiResult>($info: { error?: unknown; options: ExtraOptions }) => {
|
|
162
|
+
const { error, options } = $info;
|
|
163
|
+
|
|
164
|
+
type ErrorInfo = {
|
|
165
|
+
response?: Response;
|
|
166
|
+
errorData?: unknown;
|
|
167
|
+
message?: string;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const resolveErrorResult = (info: ErrorInfo = {}): CallApiResult => {
|
|
171
|
+
const { errorData, message, response } = info;
|
|
172
|
+
|
|
173
|
+
const shouldThrowOnError = isFunction(options.throwOnError)
|
|
174
|
+
? options.throwOnError(error as Error)
|
|
175
|
+
: options.throwOnError;
|
|
176
|
+
|
|
177
|
+
if (shouldThrowOnError) {
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
dataInfo: null,
|
|
183
|
+
errorInfo: {
|
|
184
|
+
errorName: (error as PossibleErrorObject)?.name ?? "UnknownError",
|
|
185
|
+
message: message ?? (error as PossibleErrorObject)?.message ?? options.defaultErrorMessage,
|
|
186
|
+
...(Boolean(errorData) && { errorData }),
|
|
187
|
+
},
|
|
188
|
+
response: response ?? null,
|
|
189
|
+
} as CallApiResult;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return resolveErrorResult;
|
|
193
|
+
};
|
|
194
|
+
export const isHTTPErrorInfo = (
|
|
195
|
+
errorInfo: Record<string, unknown> | null
|
|
196
|
+
): errorInfo is { errorName: "HTTPError" } => isObject(errorInfo) && errorInfo.errorName === "HTTPError";
|
|
197
|
+
|
|
198
|
+
type ErrorDetails<TErrorResponse> = {
|
|
199
|
+
response: Response & { errorData: TErrorResponse };
|
|
200
|
+
defaultErrorMessage: string;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
type ErrorOptions = {
|
|
204
|
+
cause?: unknown;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {
|
|
208
|
+
response: ErrorDetails<TErrorResponse>["response"];
|
|
209
|
+
|
|
210
|
+
override name = "HTTPError" as const;
|
|
211
|
+
|
|
212
|
+
isHTTPError = true;
|
|
213
|
+
|
|
214
|
+
constructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions) {
|
|
215
|
+
const { defaultErrorMessage, response } = errorDetails;
|
|
216
|
+
|
|
217
|
+
super((response.errorData as { message?: string }).message ?? defaultErrorMessage, errorOptions);
|
|
218
|
+
|
|
219
|
+
this.response = response;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const isHTTPErrorInstance = <TErrorResponse>(
|
|
224
|
+
error: unknown
|
|
225
|
+
): error is HTTPError<TErrorResponse> => {
|
|
226
|
+
return (
|
|
227
|
+
error instanceof HTTPError ||
|
|
228
|
+
(isObject(error) && error.name === "HTTPError" && error.isHTTPError === true)
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const wait = (delay: number) => {
|
|
233
|
+
if (delay === 0) return;
|
|
234
|
+
|
|
235
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
236
|
+
|
|
237
|
+
setTimeout(resolve, delay);
|
|
238
|
+
|
|
239
|
+
return promise;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const isArray = <TArray>(value: unknown): value is TArray[] => Array.isArray(value);
|
|
243
|
+
|
|
244
|
+
export const isFormData = (value: unknown) => value instanceof FormData;
|
|
245
|
+
|
|
246
|
+
export const isObject = <TObject extends Record<string, unknown>>(value: unknown): value is TObject => {
|
|
247
|
+
return typeof value === "object" && value !== null && !isFormData(value) && !Array.isArray(value);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const isFunction = <TFunction extends AnyFunction>(value: unknown): value is TFunction =>
|
|
251
|
+
typeof value === "function";
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var utils = require('./utils');
|
|
4
|
-
|
|
5
|
-
const m=C=>{const l=new Map,[D,A]=utils.splitConfig(C??{}),{headers:p,signal:w,...M}=D,o=async(s,f)=>{const[q,S]=utils.splitConfig(f??{}),e={bodySerializer:JSON.stringify,responseType:"json",baseURL:"",retries:0,retryDelay:0,retryCodes:utils.defaultRetryCodes,retryMethods:utils.defaultRetryMethods,defaultErrorMessage:"Failed to fetch data from server!",cancelRedundantRequests:!0,...A,...S},{signal:O=w,body:a,headers:T,...x}=q,g=l.get(s);if(g&&e.cancelRedundantRequests){const t=new DOMException("Cancelled the previous unfinished request","AbortError");g.abort(t);}const c=new AbortController;l.set(s,c);const B=e.timeout?AbortSignal.timeout(e.timeout):null,E=AbortSignal.any([c.signal,B??c.signal,O??c.signal]),n={signal:E,method:"GET",body:utils.isObject(a)?e.bodySerializer(a):a,headers:p||T||utils.isObject(a)?{...utils.isObject(a)&&{"Content-Type":"application/json",Accept:"application/json"},...utils.isFormData(a)&&{"Content-Type":"multipart/form-data"},...utils.objectifyHeaders(p),...utils.objectifyHeaders(T)}:void 0,...M,...x};try{await e.onRequest?.({request:n,options:e});const t=await fetch(`${e.baseURL}${utils.mergeUrlWithParams(s,e.query)}`,n);if(!E.aborted&&e.retries!==0&&!t.ok&&e.retryCodes.includes(t.status)&&e.retryMethods.includes(n.method))return await utils.wait(e.retryDelay),await o(s,{...f,retries:e.retries-1});if(!t.ok){const u=await utils.getResponseData(t,e.responseType,e.responseParser);throw new utils.HTTPError({response:{...t,errorData:u},defaultErrorMessage:e.defaultErrorMessage})}const r=await utils.getResponseData(t,e.responseType,e.responseParser);return await e.onResponse?.({response:{...t.clone(),data:r},request:n,options:e}),utils.resolveSuccessResult({successData:r,response:t.clone(),options:e})}catch(t){const i=utils.$resolveErrorResult({error:t,options:e});if(t instanceof DOMException&&t.name==="TimeoutError"){const r=`Request timed out after ${e.timeout}ms`;return console.info(`%cTimeoutError: ${r}`,"color: red; font-weight: 500; font-size: 14px;"),console.trace("TimeoutError"),i({message:r})}if(t instanceof DOMException&&t.name==="AbortError"){const r="Request was cancelled";return console.info(`%AbortError: ${r}`,"color: red; font-weight: 500; font-size: 14px;"),console.trace("AbortError"),i({message:r})}if(utils.isHTTPErrorInstance(t)){const{errorData:r,...u}=t.response;return await e.onResponseError?.({response:{...u.clone(),errorData:r},request:n,options:e}),i({errorData:r,response:u.clone(),message:r?.message})}return await e.onRequestError?.({request:n,error:t,options:e}),i()}finally{l.delete(s);}};return o.create=m,o.isHTTPErrorInfo=utils.isHTTPErrorInfo,o.isHTTPErrorInstance=utils.isHTTPErrorInstance,o.cancel=s=>l.get(s)?.abort(),o},G=m();var k=G;
|
|
6
|
-
|
|
7
|
-
module.exports = k;
|
|
8
|
-
//# sourceMappingURL=out.js.map
|
|
9
|
-
//# sourceMappingURL=createFetchClient.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/createFetchClient.ts"],"names":["$resolveErrorResult","HTTPError","defaultRetryCodes","defaultRetryMethods","getResponseData","isFormData","isHTTPErrorInfo","isHTTPErrorInstance","isObject","mergeUrlWithParams","objectifyHeaders","resolveSuccessResult","splitConfig","wait","createFetchClient","baseConfig","abortControllerStore","baseFetchConfig","baseExtraOptions","baseHeaders","baseSignal","restOfBaseFetchConfig","callApi","url","config","fetchConfig","extraOptions","options","signal","body","headers","restOfFetchConfig","prevFetchController","reason","newFetchController","timeoutSignal","combinedSignal","requestInit","response","errorData","successData","error","resolveErrorResult","message","createFetchClient_default"],"mappings":"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","sourcesContent":["import type {\n\t$RequestConfig,\n\tAbortSignalWithAny,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tGetCallApiResult,\n\tPossibleErrorObject,\n\tResultStyleUnion,\n} from \"./types\";\nimport {\n\t$resolveErrorResult,\n\tHTTPError,\n\tdefaultRetryCodes,\n\tdefaultRetryMethods,\n\tgetResponseData,\n\tisFormData,\n\tisHTTPErrorInfo,\n\tisHTTPErrorInstance,\n\tisObject,\n\tmergeUrlWithParams,\n\tobjectifyHeaders,\n\tresolveSuccessResult,\n\tsplitConfig,\n\twait,\n} from \"./utils\";\n\nconst createFetchClient = <\n\tTBaseData,\n\tTBaseErrorData,\n\tTBaseResultMode extends ResultStyleUnion = undefined,\n>(\n\tbaseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>\n) => {\n\tconst abortControllerStore = new Map<string, AbortController>();\n\n\tconst [baseFetchConfig, baseExtraOptions] = splitConfig(baseConfig ?? {});\n\n\tconst { headers: baseHeaders, signal: baseSignal, ...restOfBaseFetchConfig } = baseFetchConfig;\n\n\tconst callApi = async <\n\t\tTData = TBaseData,\n\t\tTErrorData = TBaseErrorData,\n\t\tTResultMode extends ResultStyleUnion = TBaseResultMode,\n\t>(\n\t\turl: string,\n\t\tconfig?: FetchConfig<TData, TErrorData, TResultMode>\n\t): Promise<GetCallApiResult<TData, TErrorData, TResultMode>> => {\n\t\ttype CallApiResult = GetCallApiResult<TData, TErrorData, TResultMode>;\n\n\t\tconst [fetchConfig, extraOptions] = splitConfig(config ?? {});\n\n\t\tconst options = {\n\t\t\tbodySerializer: JSON.stringify,\n\t\t\tresponseType: \"json\",\n\t\t\tbaseURL: \"\",\n\t\t\tretries: 0,\n\t\t\tretryDelay: 0,\n\t\t\tretryCodes: defaultRetryCodes,\n\t\t\tretryMethods: defaultRetryMethods,\n\t\t\tdefaultErrorMessage: \"Failed to fetch data from server!\",\n\t\t\tcancelRedundantRequests: true,\n\t\t\t...baseExtraOptions,\n\t\t\t...extraOptions,\n\t\t} satisfies ExtraOptions;\n\n\t\tconst { signal = baseSignal, body, headers, ...restOfFetchConfig } = fetchConfig;\n\n\t\tconst prevFetchController = abortControllerStore.get(url);\n\n\t\tif (prevFetchController && options.cancelRedundantRequests) {\n\t\t\tconst reason = new DOMException(\"Cancelled the previous unfinished request\", \"AbortError\");\n\t\t\tprevFetchController.abort(reason);\n\t\t}\n\n\t\tconst newFetchController = new AbortController();\n\n\t\tabortControllerStore.set(url, newFetchController);\n\n\t\tconst timeoutSignal = options.timeout ? AbortSignal.timeout(options.timeout) : null;\n\n\t\t// FIXME - Remove this type cast once TS updates its lib-dom types for AbortSignal to include the any() method\n\t\tconst combinedSignal = (AbortSignal as AbortSignalWithAny).any([\n\t\t\tnewFetchController.signal,\n\t\t\ttimeoutSignal ?? newFetchController.signal,\n\t\t\tsignal ?? newFetchController.signal,\n\t\t]);\n\n\t\tconst requestInit = {\n\t\t\tsignal: combinedSignal,\n\n\t\t\tmethod: \"GET\",\n\n\t\t\tbody: isObject(body) ? options.bodySerializer(body) : body,\n\n\t\t\t// == Return undefined if there are no headers provided or if the body is not an object\n\t\t\theaders:\n\t\t\t\tbaseHeaders || headers || isObject(body)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...(isObject(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...(isFormData(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"multipart/form-data\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...objectifyHeaders(baseHeaders),\n\t\t\t\t\t\t\t...objectifyHeaders(headers),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\n\t\t\t...restOfBaseFetchConfig,\n\t\t\t...restOfFetchConfig,\n\t\t} satisfies $RequestConfig;\n\n\t\ttry {\n\t\t\tawait options.onRequest?.({ request: requestInit, options });\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${options.baseURL}${mergeUrlWithParams(url, options.query)}`,\n\t\t\t\trequestInit\n\t\t\t);\n\n\t\t\tconst shouldRetry =\n\t\t\t\t!combinedSignal.aborted &&\n\t\t\t\toptions.retries !== 0 &&\n\t\t\t\t!response.ok &&\n\t\t\t\toptions.retryCodes.includes(response.status) &&\n\t\t\t\toptions.retryMethods.includes(requestInit.method);\n\n\t\t\tif (shouldRetry) {\n\t\t\t\tawait wait(options.retryDelay);\n\n\t\t\t\treturn await callApi(url, { ...config, retries: options.retries - 1 });\n\t\t\t}\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorData = await getResponseData<TErrorData>(\n\t\t\t\t\tresponse,\n\t\t\t\t\toptions.responseType,\n\t\t\t\t\toptions.responseParser\n\t\t\t\t);\n\n\t\t\t\t// == Pushing all error handling responsibilities to the catch block\n\t\t\t\tthrow new HTTPError({\n\t\t\t\t\tresponse: { ...response, errorData },\n\t\t\t\t\tdefaultErrorMessage: options.defaultErrorMessage,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst successData = await getResponseData<TData>(\n\t\t\t\tresponse,\n\t\t\t\toptions.responseType,\n\t\t\t\toptions.responseParser\n\t\t\t);\n\n\t\t\tawait options.onResponse?.({\n\t\t\t\tresponse: { ...response.clone(), data: successData },\n\t\t\t\trequest: requestInit,\n\t\t\t\toptions,\n\t\t\t});\n\n\t\t\treturn resolveSuccessResult<CallApiResult>({ successData, response: response.clone(), options });\n\n\t\t\t// == Exhaustive Error handling\n\t\t} catch (error) {\n\t\t\tconst resolveErrorResult = $resolveErrorResult<CallApiResult>({ error, options });\n\n\t\t\tif (error instanceof DOMException && error.name === \"TimeoutError\") {\n\t\t\t\tconst message = `Request timed out after ${options.timeout}ms`;\n\n\t\t\t\tconsole.info(`%cTimeoutError: ${message}`, \"color: red; font-weight: 500; font-size: 14px;\");\n\t\t\t\tconsole.trace(\"TimeoutError\");\n\n\t\t\t\treturn resolveErrorResult({ message });\n\t\t\t}\n\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") {\n\t\t\t\tconst message = `Request was cancelled`;\n\n\t\t\t\tconsole.info(`%AbortError: ${message}`, \"color: red; font-weight: 500; font-size: 14px;\");\n\t\t\t\tconsole.trace(\"AbortError\");\n\n\t\t\t\treturn resolveErrorResult({ message });\n\t\t\t}\n\n\t\t\tif (isHTTPErrorInstance<TErrorData>(error)) {\n\t\t\t\tconst { errorData, ...response } = error.response;\n\n\t\t\t\tawait options.onResponseError?.({\n\t\t\t\t\tresponse: { ...response.clone(), errorData },\n\t\t\t\t\trequest: requestInit,\n\t\t\t\t\toptions,\n\t\t\t\t});\n\n\t\t\t\treturn resolveErrorResult({\n\t\t\t\t\terrorData,\n\t\t\t\t\tresponse: response.clone(),\n\t\t\t\t\tmessage: (errorData as PossibleErrorObject)?.message,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// == At this point only the request errors exist, so the request error interceptor is called\n\t\t\tawait options.onRequestError?.({ request: requestInit, error: error as Error, options });\n\n\t\t\treturn resolveErrorResult();\n\n\t\t\t// == Removing the now unneeded AbortController from store\n\t\t} finally {\n\t\t\tabortControllerStore.delete(url);\n\t\t}\n\t};\n\n\tcallApi.create = createFetchClient;\n\tcallApi.isHTTPErrorInfo = isHTTPErrorInfo;\n\tcallApi.isHTTPErrorInstance = isHTTPErrorInstance;\n\tcallApi.cancel = (url: string) => abortControllerStore.get(url)?.abort();\n\n\treturn callApi;\n};\n\nconst callApi = createFetchClient();\n\nexport default callApi;\n"]}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { F as FetchConfig, G as GetCallApiResult, B as BaseConfig, H as HTTPError } from './types-Cf2V09St.cjs';
|
|
2
|
-
|
|
3
|
-
declare const callApi: {
|
|
4
|
-
<TData = unknown, TErrorData = unknown, TResultMode extends "all" | "onlySuccess" | "onlyError" | "onlyResponse" | undefined = undefined>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode> | undefined): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
|
|
5
|
-
create: <TBaseData, TBaseErrorData, TBaseResultMode extends "all" | "onlySuccess" | "onlyError" | "onlyResponse" | undefined = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
6
|
-
<TData_1 = TBaseData, TErrorData_1 = TBaseErrorData, TResultMode_1 extends "all" | "onlySuccess" | "onlyError" | "onlyResponse" | undefined = TBaseResultMode>(url: string, config?: FetchConfig<TData_1, TErrorData_1, TResultMode_1> | undefined): Promise<GetCallApiResult<TData_1, TErrorData_1, TResultMode_1>>;
|
|
7
|
-
create: any;
|
|
8
|
-
isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
|
|
9
|
-
errorName: "HTTPError";
|
|
10
|
-
};
|
|
11
|
-
isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
|
|
12
|
-
cancel(url: string): void | undefined;
|
|
13
|
-
};
|
|
14
|
-
isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
|
|
15
|
-
errorName: "HTTPError";
|
|
16
|
-
};
|
|
17
|
-
isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
|
|
18
|
-
cancel(url: string): void | undefined;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export { callApi as default };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { F as FetchConfig, G as GetCallApiResult, B as BaseConfig, H as HTTPError } from './types-Cf2V09St.js';
|
|
2
|
-
|
|
3
|
-
declare const callApi: {
|
|
4
|
-
<TData = unknown, TErrorData = unknown, TResultMode extends "all" | "onlySuccess" | "onlyError" | "onlyResponse" | undefined = undefined>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode> | undefined): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
|
|
5
|
-
create: <TBaseData, TBaseErrorData, TBaseResultMode extends "all" | "onlySuccess" | "onlyError" | "onlyResponse" | undefined = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
6
|
-
<TData_1 = TBaseData, TErrorData_1 = TBaseErrorData, TResultMode_1 extends "all" | "onlySuccess" | "onlyError" | "onlyResponse" | undefined = TBaseResultMode>(url: string, config?: FetchConfig<TData_1, TErrorData_1, TResultMode_1> | undefined): Promise<GetCallApiResult<TData_1, TErrorData_1, TResultMode_1>>;
|
|
7
|
-
create: any;
|
|
8
|
-
isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
|
|
9
|
-
errorName: "HTTPError";
|
|
10
|
-
};
|
|
11
|
-
isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
|
|
12
|
-
cancel(url: string): void | undefined;
|
|
13
|
-
};
|
|
14
|
-
isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
|
|
15
|
-
errorName: "HTTPError";
|
|
16
|
-
};
|
|
17
|
-
isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
|
|
18
|
-
cancel(url: string): void | undefined;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export { callApi as default };
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { splitConfig, isHTTPErrorInfo, isHTTPErrorInstance, defaultRetryCodes, defaultRetryMethods, isObject, isFormData, objectifyHeaders, mergeUrlWithParams, wait, getResponseData, HTTPError, resolveSuccessResult, $resolveErrorResult } from './utils';
|
|
2
|
-
|
|
3
|
-
const m=C=>{const l=new Map,[D,A]=splitConfig(C??{}),{headers:p,signal:w,...M}=D,o=async(s,f)=>{const[q,S]=splitConfig(f??{}),e={bodySerializer:JSON.stringify,responseType:"json",baseURL:"",retries:0,retryDelay:0,retryCodes:defaultRetryCodes,retryMethods:defaultRetryMethods,defaultErrorMessage:"Failed to fetch data from server!",cancelRedundantRequests:!0,...A,...S},{signal:O=w,body:a,headers:T,...x}=q,g=l.get(s);if(g&&e.cancelRedundantRequests){const t=new DOMException("Cancelled the previous unfinished request","AbortError");g.abort(t);}const c=new AbortController;l.set(s,c);const B=e.timeout?AbortSignal.timeout(e.timeout):null,E=AbortSignal.any([c.signal,B??c.signal,O??c.signal]),n={signal:E,method:"GET",body:isObject(a)?e.bodySerializer(a):a,headers:p||T||isObject(a)?{...isObject(a)&&{"Content-Type":"application/json",Accept:"application/json"},...isFormData(a)&&{"Content-Type":"multipart/form-data"},...objectifyHeaders(p),...objectifyHeaders(T)}:void 0,...M,...x};try{await e.onRequest?.({request:n,options:e});const t=await fetch(`${e.baseURL}${mergeUrlWithParams(s,e.query)}`,n);if(!E.aborted&&e.retries!==0&&!t.ok&&e.retryCodes.includes(t.status)&&e.retryMethods.includes(n.method))return await wait(e.retryDelay),await o(s,{...f,retries:e.retries-1});if(!t.ok){const u=await getResponseData(t,e.responseType,e.responseParser);throw new HTTPError({response:{...t,errorData:u},defaultErrorMessage:e.defaultErrorMessage})}const r=await getResponseData(t,e.responseType,e.responseParser);return await e.onResponse?.({response:{...t.clone(),data:r},request:n,options:e}),resolveSuccessResult({successData:r,response:t.clone(),options:e})}catch(t){const i=$resolveErrorResult({error:t,options:e});if(t instanceof DOMException&&t.name==="TimeoutError"){const r=`Request timed out after ${e.timeout}ms`;return console.info(`%cTimeoutError: ${r}`,"color: red; font-weight: 500; font-size: 14px;"),console.trace("TimeoutError"),i({message:r})}if(t instanceof DOMException&&t.name==="AbortError"){const r="Request was cancelled";return console.info(`%AbortError: ${r}`,"color: red; font-weight: 500; font-size: 14px;"),console.trace("AbortError"),i({message:r})}if(isHTTPErrorInstance(t)){const{errorData:r,...u}=t.response;return await e.onResponseError?.({response:{...u.clone(),errorData:r},request:n,options:e}),i({errorData:r,response:u.clone(),message:r?.message})}return await e.onRequestError?.({request:n,error:t,options:e}),i()}finally{l.delete(s);}};return o.create=m,o.isHTTPErrorInfo=isHTTPErrorInfo,o.isHTTPErrorInstance=isHTTPErrorInstance,o.cancel=s=>l.get(s)?.abort(),o},G=m();var k=G;
|
|
4
|
-
|
|
5
|
-
export { k as default };
|
|
6
|
-
//# sourceMappingURL=out.js.map
|
|
7
|
-
//# sourceMappingURL=createFetchClient.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/createFetchClient.ts"],"names":["$resolveErrorResult","HTTPError","defaultRetryCodes","defaultRetryMethods","getResponseData","isFormData","isHTTPErrorInfo","isHTTPErrorInstance","isObject","mergeUrlWithParams","objectifyHeaders","resolveSuccessResult","splitConfig","wait","createFetchClient","baseConfig","abortControllerStore","baseFetchConfig","baseExtraOptions","baseHeaders","baseSignal","restOfBaseFetchConfig","callApi","url","config","fetchConfig","extraOptions","options","signal","body","headers","restOfFetchConfig","prevFetchController","reason","newFetchController","timeoutSignal","combinedSignal","requestInit","response","errorData","successData","error","resolveErrorResult","message","createFetchClient_default"],"mappings":"AAUA,OACC,uBAAAA,EACA,aAAAC,EACA,qBAAAC,EACA,uBAAAC,EACA,mBAAAC,EACA,cAAAC,EACA,mBAAAC,EACA,uBAAAC,EACA,YAAAC,EACA,sBAAAC,EACA,oBAAAC,EACA,wBAAAC,EACA,eAAAC,EACA,QAAAC,MACM,UAEP,MAAMC,EAKLC,GACI,CACJ,MAAMC,EAAuB,IAAI,IAE3B,CAACC,EAAiBC,CAAgB,EAAIN,EAAYG,GAAc,CAAC,CAAC,EAElE,CAAE,QAASI,EAAa,OAAQC,EAAY,GAAGC,CAAsB,EAAIJ,EAEzEK,EAAU,MAKfC,EACAC,IAC+D,CAG/D,KAAM,CAACC,EAAaC,CAAY,EAAId,EAAYY,GAAU,CAAC,CAAC,EAEtDG,EAAU,CACf,eAAgB,KAAK,UACrB,aAAc,OACd,QAAS,GACT,QAAS,EACT,WAAY,EACZ,WAAYzB,EACZ,aAAcC,EACd,oBAAqB,oCACrB,wBAAyB,GACzB,GAAGe,EACH,GAAGQ,CACJ,EAEM,CAAE,OAAAE,EAASR,EAAY,KAAAS,EAAM,QAAAC,EAAS,GAAGC,CAAkB,EAAIN,EAE/DO,EAAsBhB,EAAqB,IAAIO,CAAG,EAExD,GAAIS,GAAuBL,EAAQ,wBAAyB,CAC3D,MAAMM,EAAS,IAAI,aAAa,4CAA6C,YAAY,EACzFD,EAAoB,MAAMC,CAAM,CACjC,CAEA,MAAMC,EAAqB,IAAI,gBAE/BlB,EAAqB,IAAIO,EAAKW,CAAkB,EAEhD,MAAMC,EAAgBR,EAAQ,QAAU,YAAY,QAAQA,EAAQ,OAAO,EAAI,KAGzES,EAAkB,YAAmC,IAAI,CAC9DF,EAAmB,OACnBC,GAAiBD,EAAmB,OACpCN,GAAUM,EAAmB,MAC9B,CAAC,EAEKG,EAAc,CACnB,OAAQD,EAER,OAAQ,MAER,KAAM5B,EAASqB,CAAI,EAAIF,EAAQ,eAAeE,CAAI,EAAIA,EAGtD,QACCV,GAAeW,GAAWtB,EAASqB,CAAI,EACpC,CACA,GAAIrB,EAASqB,CAAI,GAAK,CACrB,eAAgB,mBAChB,OAAQ,kBACT,EACA,GAAIxB,EAAWwB,CAAI,GAAK,CACvB,eAAgB,qBACjB,EACA,GAAGnB,EAAiBS,CAAW,EAC/B,GAAGT,EAAiBoB,CAAO,CAC5B,EACC,OAEJ,GAAGT,EACH,GAAGU,CACJ,EAEA,GAAI,CACH,MAAMJ,EAAQ,YAAY,CAAE,QAASU,EAAa,QAAAV,CAAQ,CAAC,EAE3D,MAAMW,EAAW,MAAM,MACtB,GAAGX,EAAQ,OAAO,GAAGlB,EAAmBc,EAAKI,EAAQ,KAAK,CAAC,GAC3DU,CACD,EASA,GANC,CAACD,EAAe,SAChBT,EAAQ,UAAY,GACpB,CAACW,EAAS,IACVX,EAAQ,WAAW,SAASW,EAAS,MAAM,GAC3CX,EAAQ,aAAa,SAASU,EAAY,MAAM,EAGhD,aAAMxB,EAAKc,EAAQ,UAAU,EAEtB,MAAML,EAAQC,EAAK,CAAE,GAAGC,EAAQ,QAASG,EAAQ,QAAU,CAAE,CAAC,EAGtE,GAAI,CAACW,EAAS,GAAI,CACjB,MAAMC,EAAY,MAAMnC,EACvBkC,EACAX,EAAQ,aACRA,EAAQ,cACT,EAGA,MAAM,IAAI1B,EAAU,CACnB,SAAU,CAAE,GAAGqC,EAAU,UAAAC,CAAU,EACnC,oBAAqBZ,EAAQ,mBAC9B,CAAC,CACF,CAEA,MAAMa,EAAc,MAAMpC,EACzBkC,EACAX,EAAQ,aACRA,EAAQ,cACT,EAEA,aAAMA,EAAQ,aAAa,CAC1B,SAAU,CAAE,GAAGW,EAAS,MAAM,EAAG,KAAME,CAAY,EACnD,QAASH,EACT,QAAAV,CACD,CAAC,EAEMhB,EAAoC,CAAE,YAAA6B,EAAa,SAAUF,EAAS,MAAM,EAAG,QAAAX,CAAQ,CAAC,CAGhG,OAASc,EAAO,CACf,MAAMC,EAAqB1C,EAAmC,CAAE,MAAAyC,EAAO,QAAAd,CAAQ,CAAC,EAEhF,GAAIc,aAAiB,cAAgBA,EAAM,OAAS,eAAgB,CACnE,MAAME,EAAU,2BAA2BhB,EAAQ,OAAO,KAE1D,eAAQ,KAAK,mBAAmBgB,CAAO,GAAI,gDAAgD,EAC3F,QAAQ,MAAM,cAAc,EAErBD,EAAmB,CAAE,QAAAC,CAAQ,CAAC,CACtC,CAEA,GAAIF,aAAiB,cAAgBA,EAAM,OAAS,aAAc,CACjE,MAAME,EAAU,wBAEhB,eAAQ,KAAK,gBAAgBA,CAAO,GAAI,gDAAgD,EACxF,QAAQ,MAAM,YAAY,EAEnBD,EAAmB,CAAE,QAAAC,CAAQ,CAAC,CACtC,CAEA,GAAIpC,EAAgCkC,CAAK,EAAG,CAC3C,KAAM,CAAE,UAAAF,EAAW,GAAGD,CAAS,EAAIG,EAAM,SAEzC,aAAMd,EAAQ,kBAAkB,CAC/B,SAAU,CAAE,GAAGW,EAAS,MAAM,EAAG,UAAAC,CAAU,EAC3C,QAASF,EACT,QAAAV,CACD,CAAC,EAEMe,EAAmB,CACzB,UAAAH,EACA,SAAUD,EAAS,MAAM,EACzB,QAAUC,GAAmC,OAC9C,CAAC,CACF,CAGA,aAAMZ,EAAQ,iBAAiB,CAAE,QAASU,EAAa,MAAOI,EAAgB,QAAAd,CAAQ,CAAC,EAEhFe,EAAmB,CAG3B,QAAE,CACD1B,EAAqB,OAAOO,CAAG,CAChC,CACD,EAEA,OAAAD,EAAQ,OAASR,EACjBQ,EAAQ,gBAAkBhB,EAC1BgB,EAAQ,oBAAsBf,EAC9Be,EAAQ,OAAUC,GAAgBP,EAAqB,IAAIO,CAAG,GAAG,MAAM,EAEhED,CACR,EAEMA,EAAUR,EAAkB,EAElC,IAAO8B,EAAQtB","sourcesContent":["import type {\n\t$RequestConfig,\n\tAbortSignalWithAny,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tGetCallApiResult,\n\tPossibleErrorObject,\n\tResultStyleUnion,\n} from \"./types\";\nimport {\n\t$resolveErrorResult,\n\tHTTPError,\n\tdefaultRetryCodes,\n\tdefaultRetryMethods,\n\tgetResponseData,\n\tisFormData,\n\tisHTTPErrorInfo,\n\tisHTTPErrorInstance,\n\tisObject,\n\tmergeUrlWithParams,\n\tobjectifyHeaders,\n\tresolveSuccessResult,\n\tsplitConfig,\n\twait,\n} from \"./utils\";\n\nconst createFetchClient = <\n\tTBaseData,\n\tTBaseErrorData,\n\tTBaseResultMode extends ResultStyleUnion = undefined,\n>(\n\tbaseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>\n) => {\n\tconst abortControllerStore = new Map<string, AbortController>();\n\n\tconst [baseFetchConfig, baseExtraOptions] = splitConfig(baseConfig ?? {});\n\n\tconst { headers: baseHeaders, signal: baseSignal, ...restOfBaseFetchConfig } = baseFetchConfig;\n\n\tconst callApi = async <\n\t\tTData = TBaseData,\n\t\tTErrorData = TBaseErrorData,\n\t\tTResultMode extends ResultStyleUnion = TBaseResultMode,\n\t>(\n\t\turl: string,\n\t\tconfig?: FetchConfig<TData, TErrorData, TResultMode>\n\t): Promise<GetCallApiResult<TData, TErrorData, TResultMode>> => {\n\t\ttype CallApiResult = GetCallApiResult<TData, TErrorData, TResultMode>;\n\n\t\tconst [fetchConfig, extraOptions] = splitConfig(config ?? {});\n\n\t\tconst options = {\n\t\t\tbodySerializer: JSON.stringify,\n\t\t\tresponseType: \"json\",\n\t\t\tbaseURL: \"\",\n\t\t\tretries: 0,\n\t\t\tretryDelay: 0,\n\t\t\tretryCodes: defaultRetryCodes,\n\t\t\tretryMethods: defaultRetryMethods,\n\t\t\tdefaultErrorMessage: \"Failed to fetch data from server!\",\n\t\t\tcancelRedundantRequests: true,\n\t\t\t...baseExtraOptions,\n\t\t\t...extraOptions,\n\t\t} satisfies ExtraOptions;\n\n\t\tconst { signal = baseSignal, body, headers, ...restOfFetchConfig } = fetchConfig;\n\n\t\tconst prevFetchController = abortControllerStore.get(url);\n\n\t\tif (prevFetchController && options.cancelRedundantRequests) {\n\t\t\tconst reason = new DOMException(\"Cancelled the previous unfinished request\", \"AbortError\");\n\t\t\tprevFetchController.abort(reason);\n\t\t}\n\n\t\tconst newFetchController = new AbortController();\n\n\t\tabortControllerStore.set(url, newFetchController);\n\n\t\tconst timeoutSignal = options.timeout ? AbortSignal.timeout(options.timeout) : null;\n\n\t\t// FIXME - Remove this type cast once TS updates its lib-dom types for AbortSignal to include the any() method\n\t\tconst combinedSignal = (AbortSignal as AbortSignalWithAny).any([\n\t\t\tnewFetchController.signal,\n\t\t\ttimeoutSignal ?? newFetchController.signal,\n\t\t\tsignal ?? newFetchController.signal,\n\t\t]);\n\n\t\tconst requestInit = {\n\t\t\tsignal: combinedSignal,\n\n\t\t\tmethod: \"GET\",\n\n\t\t\tbody: isObject(body) ? options.bodySerializer(body) : body,\n\n\t\t\t// == Return undefined if there are no headers provided or if the body is not an object\n\t\t\theaders:\n\t\t\t\tbaseHeaders || headers || isObject(body)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...(isObject(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...(isFormData(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"multipart/form-data\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...objectifyHeaders(baseHeaders),\n\t\t\t\t\t\t\t...objectifyHeaders(headers),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\n\t\t\t...restOfBaseFetchConfig,\n\t\t\t...restOfFetchConfig,\n\t\t} satisfies $RequestConfig;\n\n\t\ttry {\n\t\t\tawait options.onRequest?.({ request: requestInit, options });\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${options.baseURL}${mergeUrlWithParams(url, options.query)}`,\n\t\t\t\trequestInit\n\t\t\t);\n\n\t\t\tconst shouldRetry =\n\t\t\t\t!combinedSignal.aborted &&\n\t\t\t\toptions.retries !== 0 &&\n\t\t\t\t!response.ok &&\n\t\t\t\toptions.retryCodes.includes(response.status) &&\n\t\t\t\toptions.retryMethods.includes(requestInit.method);\n\n\t\t\tif (shouldRetry) {\n\t\t\t\tawait wait(options.retryDelay);\n\n\t\t\t\treturn await callApi(url, { ...config, retries: options.retries - 1 });\n\t\t\t}\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorData = await getResponseData<TErrorData>(\n\t\t\t\t\tresponse,\n\t\t\t\t\toptions.responseType,\n\t\t\t\t\toptions.responseParser\n\t\t\t\t);\n\n\t\t\t\t// == Pushing all error handling responsibilities to the catch block\n\t\t\t\tthrow new HTTPError({\n\t\t\t\t\tresponse: { ...response, errorData },\n\t\t\t\t\tdefaultErrorMessage: options.defaultErrorMessage,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst successData = await getResponseData<TData>(\n\t\t\t\tresponse,\n\t\t\t\toptions.responseType,\n\t\t\t\toptions.responseParser\n\t\t\t);\n\n\t\t\tawait options.onResponse?.({\n\t\t\t\tresponse: { ...response.clone(), data: successData },\n\t\t\t\trequest: requestInit,\n\t\t\t\toptions,\n\t\t\t});\n\n\t\t\treturn resolveSuccessResult<CallApiResult>({ successData, response: response.clone(), options });\n\n\t\t\t// == Exhaustive Error handling\n\t\t} catch (error) {\n\t\t\tconst resolveErrorResult = $resolveErrorResult<CallApiResult>({ error, options });\n\n\t\t\tif (error instanceof DOMException && error.name === \"TimeoutError\") {\n\t\t\t\tconst message = `Request timed out after ${options.timeout}ms`;\n\n\t\t\t\tconsole.info(`%cTimeoutError: ${message}`, \"color: red; font-weight: 500; font-size: 14px;\");\n\t\t\t\tconsole.trace(\"TimeoutError\");\n\n\t\t\t\treturn resolveErrorResult({ message });\n\t\t\t}\n\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") {\n\t\t\t\tconst message = `Request was cancelled`;\n\n\t\t\t\tconsole.info(`%AbortError: ${message}`, \"color: red; font-weight: 500; font-size: 14px;\");\n\t\t\t\tconsole.trace(\"AbortError\");\n\n\t\t\t\treturn resolveErrorResult({ message });\n\t\t\t}\n\n\t\t\tif (isHTTPErrorInstance<TErrorData>(error)) {\n\t\t\t\tconst { errorData, ...response } = error.response;\n\n\t\t\t\tawait options.onResponseError?.({\n\t\t\t\t\tresponse: { ...response.clone(), errorData },\n\t\t\t\t\trequest: requestInit,\n\t\t\t\t\toptions,\n\t\t\t\t});\n\n\t\t\t\treturn resolveErrorResult({\n\t\t\t\t\terrorData,\n\t\t\t\t\tresponse: response.clone(),\n\t\t\t\t\tmessage: (errorData as PossibleErrorObject)?.message,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// == At this point only the request errors exist, so the request error interceptor is called\n\t\t\tawait options.onRequestError?.({ request: requestInit, error: error as Error, options });\n\n\t\t\treturn resolveErrorResult();\n\n\t\t\t// == Removing the now unneeded AbortController from store\n\t\t} finally {\n\t\t\tabortControllerStore.delete(url);\n\t\t}\n\t};\n\n\tcallApi.create = createFetchClient;\n\tcallApi.isHTTPErrorInfo = isHTTPErrorInfo;\n\tcallApi.isHTTPErrorInstance = isHTTPErrorInstance;\n\tcallApi.cancel = (url: string) => abortControllerStore.get(url)?.abort();\n\n\treturn callApi;\n};\n\nconst callApi = createFetchClient();\n\nexport default callApi;\n"]}
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["default","isHTTPErrorInfo","HTTPError","isHTTPErrorInstance"],"mappings":"AAAA,OAAoB,WAAXA,MAA0B,sBAInC,OAAS,mBAAAC,EAAiB,aAAAC,EAAW,uBAAAC,MAA2B","sourcesContent":["export { default as callApi } from \"./createFetchClient\";\n\nexport type { FetchConfig, BaseConfig } from \"./types\";\n\nexport { isHTTPErrorInfo, HTTPError, isHTTPErrorInstance } from \"./utils\";\n"]}
|
package/dist/index.d.cts
DELETED
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["default","isHTTPErrorInfo","HTTPError","isHTTPErrorInstance"],"mappings":"AAAA,OAAoB,WAAXA,MAA0B,sBAInC,OAAS,mBAAAC,EAAiB,aAAAC,EAAW,uBAAAC,MAA2B","sourcesContent":["export { default as callApi } from \"./createFetchClient\";\n\nexport type { FetchConfig, BaseConfig } from \"./types\";\n\nexport { isHTTPErrorInfo, HTTPError, isHTTPErrorInstance } from \"./utils\";\n"]}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
type AnyString = string & {
|
|
2
|
-
placeholder?: never;
|
|
3
|
-
};
|
|
4
|
-
type AnyNumber = number & {
|
|
5
|
-
placeholder?: never;
|
|
6
|
-
};
|
|
7
|
-
type AnyFunction = (...args: any[]) => any;
|
|
8
|
-
|
|
9
|
-
declare const mergeUrlWithParams: (url: string, params: ExtraOptions["query"]) => string;
|
|
10
|
-
declare const objectifyHeaders: (headers: RequestInit["headers"]) => Record<string, string> | undefined;
|
|
11
|
-
declare const defaultRetryCodes: Required<BaseConfig>["retryCodes"];
|
|
12
|
-
declare const defaultRetryMethods: Required<BaseConfig>["retryMethods"];
|
|
13
|
-
declare const fetchSpecificKeys: ("headers" | "body" | "method" | "cache" | "credentials" | "integrity" | "keepalive" | "mode" | "priority" | "redirect" | "referrer" | "referrerPolicy" | "signal" | "window")[];
|
|
14
|
-
declare const splitConfig: <TObject extends object>(config: TObject) => ["body" extends keyof TObject ? $RequestConfig : $BaseRequestConfig, ExtraOptions];
|
|
15
|
-
declare const handleResponseType: <TResponse>(response: Response, parser?: Required<ExtraOptions>["responseParser"]) => {
|
|
16
|
-
json: () => Promise<TResponse>;
|
|
17
|
-
arrayBuffer: () => Promise<TResponse>;
|
|
18
|
-
blob: () => Promise<TResponse>;
|
|
19
|
-
formData: () => Promise<TResponse>;
|
|
20
|
-
text: () => Promise<TResponse>;
|
|
21
|
-
};
|
|
22
|
-
declare const getResponseData: <TResponse>(response: Response, responseType: keyof ReturnType<typeof handleResponseType>, parser: ExtraOptions["responseParser"]) => Promise<TResponse>;
|
|
23
|
-
type DataInfo = {
|
|
24
|
-
successData: unknown;
|
|
25
|
-
options: ExtraOptions;
|
|
26
|
-
response: Response;
|
|
27
|
-
};
|
|
28
|
-
declare const resolveSuccessResult: <CallApiResult>(info: DataInfo) => CallApiResult;
|
|
29
|
-
declare const $resolveErrorResult: <CallApiResult>($info: {
|
|
30
|
-
error?: unknown;
|
|
31
|
-
options: ExtraOptions;
|
|
32
|
-
}) => (info?: {
|
|
33
|
-
response?: Response | undefined;
|
|
34
|
-
errorData?: unknown;
|
|
35
|
-
message?: string | undefined;
|
|
36
|
-
}) => CallApiResult;
|
|
37
|
-
declare const isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
|
|
38
|
-
errorName: "HTTPError";
|
|
39
|
-
};
|
|
40
|
-
type ErrorDetails<TErrorResponse> = {
|
|
41
|
-
response: Response & {
|
|
42
|
-
errorData: TErrorResponse;
|
|
43
|
-
};
|
|
44
|
-
defaultErrorMessage: string;
|
|
45
|
-
};
|
|
46
|
-
type ErrorOptions = {
|
|
47
|
-
cause?: unknown;
|
|
48
|
-
};
|
|
49
|
-
declare class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {
|
|
50
|
-
response: ErrorDetails<TErrorResponse>["response"];
|
|
51
|
-
name: "HTTPError";
|
|
52
|
-
isHTTPError: boolean;
|
|
53
|
-
constructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions);
|
|
54
|
-
}
|
|
55
|
-
declare const isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
|
|
56
|
-
declare const wait: (delay: number) => Promise<unknown> | undefined;
|
|
57
|
-
declare const isFormData: (value: unknown) => boolean;
|
|
58
|
-
declare const isObject: <TObject extends Record<string, unknown>>(value: unknown) => value is TObject;
|
|
59
|
-
declare const isFunction: <TFunction extends AnyFunction>(value: unknown) => value is TFunction;
|
|
60
|
-
|
|
61
|
-
type $RequestConfig = Pick<FetchConfig, (typeof fetchSpecificKeys)[number]>;
|
|
62
|
-
type $BaseRequestConfig = Omit<$RequestConfig, "body">;
|
|
63
|
-
type ExtraOptions<TBaseData = unknown, TBaseErrorData = unknown, TBaseResultMode extends ResultStyleUnion = ResultStyleUnion> = {
|
|
64
|
-
body?: Record<string, unknown> | RequestInit["body"];
|
|
65
|
-
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE" | AnyString;
|
|
66
|
-
query?: Record<string, string | number | boolean>;
|
|
67
|
-
bodySerializer?: (bodyData: Record<string, unknown>) => string;
|
|
68
|
-
responseParser?: {
|
|
69
|
-
(data: string): unknown;
|
|
70
|
-
<TData>(data: string): TData;
|
|
71
|
-
};
|
|
72
|
-
resultMode?: TBaseResultMode;
|
|
73
|
-
cancelRedundantRequests?: boolean;
|
|
74
|
-
baseURL?: string;
|
|
75
|
-
timeout?: number;
|
|
76
|
-
defaultErrorMessage?: string;
|
|
77
|
-
throwOnError?: boolean | ((error?: Error | HTTPError<TBaseErrorData>) => boolean);
|
|
78
|
-
responseType?: keyof ReturnType<typeof handleResponseType>;
|
|
79
|
-
retries?: number;
|
|
80
|
-
retryDelay?: number;
|
|
81
|
-
retryCodes?: Array<409 | 425 | 429 | 500 | 502 | 503 | 504 | AnyNumber>;
|
|
82
|
-
retryMethods?: Array<"GET" | "POST" | "PATCH" | "DELETE" | AnyString>;
|
|
83
|
-
meta?: Record<string, unknown>;
|
|
84
|
-
onRequest?: (requestContext: {
|
|
85
|
-
request: $RequestConfig;
|
|
86
|
-
options: ExtraOptions;
|
|
87
|
-
}) => void | Promise<void>;
|
|
88
|
-
onRequestError?: (requestContext: {
|
|
89
|
-
request: $RequestConfig;
|
|
90
|
-
error: Error;
|
|
91
|
-
options: ExtraOptions;
|
|
92
|
-
}) => void | Promise<void>;
|
|
93
|
-
onResponse?: (successContext: {
|
|
94
|
-
request: $RequestConfig;
|
|
95
|
-
response: Response & {
|
|
96
|
-
data: TBaseData;
|
|
97
|
-
};
|
|
98
|
-
options: ExtraOptions;
|
|
99
|
-
}) => void | Promise<void>;
|
|
100
|
-
onResponseError?: (errorContext: {
|
|
101
|
-
request: $RequestConfig;
|
|
102
|
-
response: Response & {
|
|
103
|
-
errorData: TBaseErrorData;
|
|
104
|
-
};
|
|
105
|
-
options: ExtraOptions;
|
|
106
|
-
}) => void | Promise<void>;
|
|
107
|
-
};
|
|
108
|
-
interface FetchConfig<TData = unknown, TErrorData = unknown, TResultMode extends ResultStyleUnion = undefined> extends Omit<RequestInit, "method" | "body">, ExtraOptions<TData, TErrorData, TResultMode> {
|
|
109
|
-
}
|
|
110
|
-
interface BaseConfig<TBaseData = unknown, TBaseErrorData = unknown, TBaseResultMode extends ResultStyleUnion = undefined> extends Omit<FetchConfig<TBaseData, TBaseErrorData, TBaseResultMode>, "body"> {
|
|
111
|
-
}
|
|
112
|
-
type ApiSuccessVariant<TData> = {
|
|
113
|
-
dataInfo: TData;
|
|
114
|
-
errorInfo: null;
|
|
115
|
-
response: Response;
|
|
116
|
-
};
|
|
117
|
-
type PossibleErrors = "AbortError" | "TimeoutError" | "SyntaxError" | "TypeError" | "Error" | "UnknownError";
|
|
118
|
-
type ApiErrorVariant<TErrorData> = {
|
|
119
|
-
dataInfo: null;
|
|
120
|
-
errorInfo: {
|
|
121
|
-
errorName: "HTTPError";
|
|
122
|
-
errorData: TErrorData;
|
|
123
|
-
message: string;
|
|
124
|
-
};
|
|
125
|
-
response: Response;
|
|
126
|
-
} | {
|
|
127
|
-
dataInfo: null;
|
|
128
|
-
errorInfo: {
|
|
129
|
-
errorName: PossibleErrors;
|
|
130
|
-
message: string;
|
|
131
|
-
};
|
|
132
|
-
response: null;
|
|
133
|
-
};
|
|
134
|
-
type ResultStyleMap<TData = unknown, TErrorData = unknown> = {
|
|
135
|
-
all: ApiSuccessVariant<TData> | ApiErrorVariant<TErrorData>;
|
|
136
|
-
onlySuccess: TData;
|
|
137
|
-
onlyError: TErrorData;
|
|
138
|
-
onlyResponse: Response;
|
|
139
|
-
};
|
|
140
|
-
type ResultStyleUnion = {
|
|
141
|
-
_: {
|
|
142
|
-
[Key in keyof ResultStyleMap]: Key;
|
|
143
|
-
}[keyof ResultStyleMap] | undefined;
|
|
144
|
-
}["_"];
|
|
145
|
-
type GetCallApiResult<TData, TErrorData, TResultMode> = TResultMode extends NonNullable<ResultStyleUnion> ? ResultStyleMap<TData, TErrorData>[TResultMode] : ResultStyleMap<TData, TErrorData>["all"];
|
|
146
|
-
|
|
147
|
-
export { $resolveErrorResult as $, type BaseConfig as B, type FetchConfig as F, type GetCallApiResult as G, HTTPError as H, isHTTPErrorInstance as a, defaultRetryMethods as b, isFormData as c, defaultRetryCodes as d, isObject as e, fetchSpecificKeys as f, getResponseData as g, handleResponseType as h, isHTTPErrorInfo as i, isFunction as j, mergeUrlWithParams as m, objectifyHeaders as o, resolveSuccessResult as r, splitConfig as s, wait as w };
|
package/dist/types-Cf2V09St.d.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
type AnyString = string & {
|
|
2
|
-
placeholder?: never;
|
|
3
|
-
};
|
|
4
|
-
type AnyNumber = number & {
|
|
5
|
-
placeholder?: never;
|
|
6
|
-
};
|
|
7
|
-
type AnyFunction = (...args: any[]) => any;
|
|
8
|
-
|
|
9
|
-
declare const mergeUrlWithParams: (url: string, params: ExtraOptions["query"]) => string;
|
|
10
|
-
declare const objectifyHeaders: (headers: RequestInit["headers"]) => Record<string, string> | undefined;
|
|
11
|
-
declare const defaultRetryCodes: Required<BaseConfig>["retryCodes"];
|
|
12
|
-
declare const defaultRetryMethods: Required<BaseConfig>["retryMethods"];
|
|
13
|
-
declare const fetchSpecificKeys: ("headers" | "body" | "method" | "cache" | "credentials" | "integrity" | "keepalive" | "mode" | "priority" | "redirect" | "referrer" | "referrerPolicy" | "signal" | "window")[];
|
|
14
|
-
declare const splitConfig: <TObject extends object>(config: TObject) => ["body" extends keyof TObject ? $RequestConfig : $BaseRequestConfig, ExtraOptions];
|
|
15
|
-
declare const handleResponseType: <TResponse>(response: Response, parser?: Required<ExtraOptions>["responseParser"]) => {
|
|
16
|
-
json: () => Promise<TResponse>;
|
|
17
|
-
arrayBuffer: () => Promise<TResponse>;
|
|
18
|
-
blob: () => Promise<TResponse>;
|
|
19
|
-
formData: () => Promise<TResponse>;
|
|
20
|
-
text: () => Promise<TResponse>;
|
|
21
|
-
};
|
|
22
|
-
declare const getResponseData: <TResponse>(response: Response, responseType: keyof ReturnType<typeof handleResponseType>, parser: ExtraOptions["responseParser"]) => Promise<TResponse>;
|
|
23
|
-
type DataInfo = {
|
|
24
|
-
successData: unknown;
|
|
25
|
-
options: ExtraOptions;
|
|
26
|
-
response: Response;
|
|
27
|
-
};
|
|
28
|
-
declare const resolveSuccessResult: <CallApiResult>(info: DataInfo) => CallApiResult;
|
|
29
|
-
declare const $resolveErrorResult: <CallApiResult>($info: {
|
|
30
|
-
error?: unknown;
|
|
31
|
-
options: ExtraOptions;
|
|
32
|
-
}) => (info?: {
|
|
33
|
-
response?: Response | undefined;
|
|
34
|
-
errorData?: unknown;
|
|
35
|
-
message?: string | undefined;
|
|
36
|
-
}) => CallApiResult;
|
|
37
|
-
declare const isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
|
|
38
|
-
errorName: "HTTPError";
|
|
39
|
-
};
|
|
40
|
-
type ErrorDetails<TErrorResponse> = {
|
|
41
|
-
response: Response & {
|
|
42
|
-
errorData: TErrorResponse;
|
|
43
|
-
};
|
|
44
|
-
defaultErrorMessage: string;
|
|
45
|
-
};
|
|
46
|
-
type ErrorOptions = {
|
|
47
|
-
cause?: unknown;
|
|
48
|
-
};
|
|
49
|
-
declare class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {
|
|
50
|
-
response: ErrorDetails<TErrorResponse>["response"];
|
|
51
|
-
name: "HTTPError";
|
|
52
|
-
isHTTPError: boolean;
|
|
53
|
-
constructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions);
|
|
54
|
-
}
|
|
55
|
-
declare const isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
|
|
56
|
-
declare const wait: (delay: number) => Promise<unknown> | undefined;
|
|
57
|
-
declare const isFormData: (value: unknown) => boolean;
|
|
58
|
-
declare const isObject: <TObject extends Record<string, unknown>>(value: unknown) => value is TObject;
|
|
59
|
-
declare const isFunction: <TFunction extends AnyFunction>(value: unknown) => value is TFunction;
|
|
60
|
-
|
|
61
|
-
type $RequestConfig = Pick<FetchConfig, (typeof fetchSpecificKeys)[number]>;
|
|
62
|
-
type $BaseRequestConfig = Omit<$RequestConfig, "body">;
|
|
63
|
-
type ExtraOptions<TBaseData = unknown, TBaseErrorData = unknown, TBaseResultMode extends ResultStyleUnion = ResultStyleUnion> = {
|
|
64
|
-
body?: Record<string, unknown> | RequestInit["body"];
|
|
65
|
-
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE" | AnyString;
|
|
66
|
-
query?: Record<string, string | number | boolean>;
|
|
67
|
-
bodySerializer?: (bodyData: Record<string, unknown>) => string;
|
|
68
|
-
responseParser?: {
|
|
69
|
-
(data: string): unknown;
|
|
70
|
-
<TData>(data: string): TData;
|
|
71
|
-
};
|
|
72
|
-
resultMode?: TBaseResultMode;
|
|
73
|
-
cancelRedundantRequests?: boolean;
|
|
74
|
-
baseURL?: string;
|
|
75
|
-
timeout?: number;
|
|
76
|
-
defaultErrorMessage?: string;
|
|
77
|
-
throwOnError?: boolean | ((error?: Error | HTTPError<TBaseErrorData>) => boolean);
|
|
78
|
-
responseType?: keyof ReturnType<typeof handleResponseType>;
|
|
79
|
-
retries?: number;
|
|
80
|
-
retryDelay?: number;
|
|
81
|
-
retryCodes?: Array<409 | 425 | 429 | 500 | 502 | 503 | 504 | AnyNumber>;
|
|
82
|
-
retryMethods?: Array<"GET" | "POST" | "PATCH" | "DELETE" | AnyString>;
|
|
83
|
-
meta?: Record<string, unknown>;
|
|
84
|
-
onRequest?: (requestContext: {
|
|
85
|
-
request: $RequestConfig;
|
|
86
|
-
options: ExtraOptions;
|
|
87
|
-
}) => void | Promise<void>;
|
|
88
|
-
onRequestError?: (requestContext: {
|
|
89
|
-
request: $RequestConfig;
|
|
90
|
-
error: Error;
|
|
91
|
-
options: ExtraOptions;
|
|
92
|
-
}) => void | Promise<void>;
|
|
93
|
-
onResponse?: (successContext: {
|
|
94
|
-
request: $RequestConfig;
|
|
95
|
-
response: Response & {
|
|
96
|
-
data: TBaseData;
|
|
97
|
-
};
|
|
98
|
-
options: ExtraOptions;
|
|
99
|
-
}) => void | Promise<void>;
|
|
100
|
-
onResponseError?: (errorContext: {
|
|
101
|
-
request: $RequestConfig;
|
|
102
|
-
response: Response & {
|
|
103
|
-
errorData: TBaseErrorData;
|
|
104
|
-
};
|
|
105
|
-
options: ExtraOptions;
|
|
106
|
-
}) => void | Promise<void>;
|
|
107
|
-
};
|
|
108
|
-
interface FetchConfig<TData = unknown, TErrorData = unknown, TResultMode extends ResultStyleUnion = undefined> extends Omit<RequestInit, "method" | "body">, ExtraOptions<TData, TErrorData, TResultMode> {
|
|
109
|
-
}
|
|
110
|
-
interface BaseConfig<TBaseData = unknown, TBaseErrorData = unknown, TBaseResultMode extends ResultStyleUnion = undefined> extends Omit<FetchConfig<TBaseData, TBaseErrorData, TBaseResultMode>, "body"> {
|
|
111
|
-
}
|
|
112
|
-
type ApiSuccessVariant<TData> = {
|
|
113
|
-
dataInfo: TData;
|
|
114
|
-
errorInfo: null;
|
|
115
|
-
response: Response;
|
|
116
|
-
};
|
|
117
|
-
type PossibleErrors = "AbortError" | "TimeoutError" | "SyntaxError" | "TypeError" | "Error" | "UnknownError";
|
|
118
|
-
type ApiErrorVariant<TErrorData> = {
|
|
119
|
-
dataInfo: null;
|
|
120
|
-
errorInfo: {
|
|
121
|
-
errorName: "HTTPError";
|
|
122
|
-
errorData: TErrorData;
|
|
123
|
-
message: string;
|
|
124
|
-
};
|
|
125
|
-
response: Response;
|
|
126
|
-
} | {
|
|
127
|
-
dataInfo: null;
|
|
128
|
-
errorInfo: {
|
|
129
|
-
errorName: PossibleErrors;
|
|
130
|
-
message: string;
|
|
131
|
-
};
|
|
132
|
-
response: null;
|
|
133
|
-
};
|
|
134
|
-
type ResultStyleMap<TData = unknown, TErrorData = unknown> = {
|
|
135
|
-
all: ApiSuccessVariant<TData> | ApiErrorVariant<TErrorData>;
|
|
136
|
-
onlySuccess: TData;
|
|
137
|
-
onlyError: TErrorData;
|
|
138
|
-
onlyResponse: Response;
|
|
139
|
-
};
|
|
140
|
-
type ResultStyleUnion = {
|
|
141
|
-
_: {
|
|
142
|
-
[Key in keyof ResultStyleMap]: Key;
|
|
143
|
-
}[keyof ResultStyleMap] | undefined;
|
|
144
|
-
}["_"];
|
|
145
|
-
type GetCallApiResult<TData, TErrorData, TResultMode> = TResultMode extends NonNullable<ResultStyleUnion> ? ResultStyleMap<TData, TErrorData>[TResultMode] : ResultStyleMap<TData, TErrorData>["all"];
|
|
146
|
-
|
|
147
|
-
export { $resolveErrorResult as $, type BaseConfig as B, type FetchConfig as F, type GetCallApiResult as G, HTTPError as H, isHTTPErrorInstance as a, defaultRetryMethods as b, isFormData as c, defaultRetryCodes as d, isObject as e, fetchSpecificKeys as f, getResponseData as g, handleResponseType as h, isHTTPErrorInfo as i, isFunction as j, mergeUrlWithParams as m, objectifyHeaders as o, resolveSuccessResult as r, splitConfig as s, wait as w };
|
package/dist/utils.cjs
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const m=(e,r)=>{if(!r)return e;const o=new URLSearchParams(r).toString();return e.includes("?")&&!e.endsWith("?")?`${e}&${o}`:e.endsWith("?")?`${e}${o}`:`${e}?${o}`},b=e=>!e||i(e)?e:Object.fromEntries(d(e)?e:e.entries()),p={408:"Request Timeout",409:"Conflict",425:"Too Early",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},g=Object.keys(p).map(Number),j=["GET"],c=["body","integrity","method","headers","signal","cache","redirect","window","credentials","keepalive","referrer","priority","mode","referrerPolicy"],l=(e,r)=>{const o=Object.entries(e).filter(([s])=>!r.includes(s));return Object.fromEntries(o)},T=(e,r)=>{const o=new Set(r),s=Object.entries(e).filter(([a])=>o.has(a));return Object.fromEntries(s)},x=e=>[T(e,c),l(e,c)],f=(e,r)=>({json:async()=>r?r(await e.text()):e.json(),arrayBuffer:()=>e.arrayBuffer(),blob:()=>e.blob(),formData:()=>e.formData(),text:()=>e.text()}),w=(e,r,o)=>{const t=f(e,o);if(!Object.hasOwn(t,r))throw new Error(`Invalid response type: ${r}`);return t[r]()},k=e=>{const{options:r,response:o,successData:t}=e,s={dataInfo:t,errorInfo:null,response:o};return r.resultMode===void 0||r.resultMode==="all"?s:{onlySuccess:s.dataInfo,onlyError:s.errorInfo,onlyResponse:s.response}[r.resultMode]},P=e=>{const{error:r,options:o}=e;return (s={})=>{const{errorData:n,message:a,response:u}=s;if(E(o.throwOnError)?o.throwOnError(r):o.throwOnError)throw r;return {dataInfo:null,errorInfo:{errorName:r?.name??"UnknownError",message:a??r?.message??o.defaultErrorMessage,...!!n&&{errorData:n}},response:u??null}}},A=e=>i(e)&&e.errorName==="HTTPError";class y extends Error{response;name="HTTPError";isHTTPError=!0;constructor(r,o){const{defaultErrorMessage:t,response:s}=r;super(s.errorData.message??t,o),this.response=s;}}const h=e=>e instanceof y||i(e)&&e.name==="HTTPError"&&e.isHTTPError===!0,C=e=>{if(e===0)return;const{promise:r,resolve:o}=Promise.withResolvers();return setTimeout(o,e),r},d=e=>Array.isArray(e),R=e=>e instanceof FormData,i=e=>typeof e=="object"&&e!==null&&!R(e)&&!Array.isArray(e),E=e=>typeof e=="function";
|
|
4
|
-
|
|
5
|
-
exports.$resolveErrorResult = P;
|
|
6
|
-
exports.HTTPError = y;
|
|
7
|
-
exports.defaultRetryCodes = g;
|
|
8
|
-
exports.defaultRetryMethods = j;
|
|
9
|
-
exports.fetchSpecificKeys = c;
|
|
10
|
-
exports.getResponseData = w;
|
|
11
|
-
exports.handleResponseType = f;
|
|
12
|
-
exports.isFormData = R;
|
|
13
|
-
exports.isFunction = E;
|
|
14
|
-
exports.isHTTPErrorInfo = A;
|
|
15
|
-
exports.isHTTPErrorInstance = h;
|
|
16
|
-
exports.isObject = i;
|
|
17
|
-
exports.mergeUrlWithParams = m;
|
|
18
|
-
exports.objectifyHeaders = b;
|
|
19
|
-
exports.resolveSuccessResult = k;
|
|
20
|
-
exports.splitConfig = x;
|
|
21
|
-
exports.wait = C;
|
|
22
|
-
//# sourceMappingURL=out.js.map
|
|
23
|
-
//# sourceMappingURL=utils.cjs.map
|
package/dist/utils.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils.ts"],"names":["mergeUrlWithParams","url","params","paramsString","objectifyHeaders","headers","isObject","isArray","retryCodesLookup","defaultRetryCodes","defaultRetryMethods","fetchSpecificKeys","omitKeys","initialObject","keysToOmit","arrayFromFilteredObject","key","pickKeys","keysToPick","keysToPickSet","filteredArray","objectKey","splitConfig","config","handleResponseType","response","parser","getResponseData","responseType","RESPONSE_TYPE_LOOKUP","resolveSuccessResult","info","options","successData","apiDetails","$resolveErrorResult","$info","error","errorData","message","isFunction","isHTTPErrorInfo","errorInfo","HTTPError","errorDetails","errorOptions","defaultErrorMessage","isHTTPErrorInstance","wait","delay","promise","resolve","value","isFormData"],"mappings":"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","sourcesContent":["import type { AnyFunction } from \"./type-helpers\";\nimport type {\n\t$BaseRequestConfig,\n\t$RequestConfig,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tPossibleErrorObject,\n} from \"./types\";\n\nexport const mergeUrlWithParams = (url: string, params: ExtraOptions[\"query\"]): string => {\n\tif (!params) {\n\t\treturn url;\n\t}\n\n\tconst paramsString = new URLSearchParams(params as Record<string, string>).toString();\n\n\tif (url.includes(\"?\") && !url.endsWith(\"?\")) {\n\t\treturn `${url}&${paramsString}`;\n\t}\n\n\tif (url.endsWith(\"?\")) {\n\t\treturn `${url}${paramsString}`;\n\t}\n\n\treturn `${url}?${paramsString}`;\n};\n\nexport const objectifyHeaders = (headers: RequestInit[\"headers\"]): Record<string, string> | undefined => {\n\tif (!headers || isObject(headers)) {\n\t\treturn headers;\n\t}\n\n\treturn Object.fromEntries(isArray(headers) ? headers : headers.entries());\n};\n\nconst retryCodesLookup = {\n\t408: \"Request Timeout\",\n\t409: \"Conflict\",\n\t425: \"Too Early\",\n\t429: \"Too Many Requests\",\n\t500: \"Internal Server Error\",\n\t502: \"Bad Gateway\",\n\t503: \"Service Unavailable\",\n\t504: \"Gateway Timeout\",\n};\n\nexport const defaultRetryCodes: Required<BaseConfig>[\"retryCodes\"] =\n\tObject.keys(retryCodesLookup).map(Number);\n\nexport const defaultRetryMethods: Required<BaseConfig>[\"retryMethods\"] = [\"GET\"];\n\nexport const fetchSpecificKeys = [\n\t\"body\",\n\t\"integrity\",\n\t\"method\",\n\t\"headers\",\n\t\"signal\",\n\t\"cache\",\n\t\"redirect\",\n\t\"window\",\n\t\"credentials\",\n\t\"keepalive\",\n\t\"referrer\",\n\t\"priority\",\n\t\"mode\",\n\t\"referrerPolicy\",\n] satisfies Array<keyof FetchConfig>;\n\nconst omitKeys = <TObject extends Record<string, unknown>, const TOmitArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToOmit: TOmitArray\n) => {\n\tconst arrayFromFilteredObject = Object.entries(initialObject).filter(\n\t\t([key]) => !keysToOmit.includes(key)\n\t);\n\n\tconst updatedObject = Object.fromEntries(arrayFromFilteredObject);\n\n\treturn updatedObject as Omit<TObject, keyof TOmitArray>;\n};\n\nconst pickKeys = <TObject extends Record<string, unknown>, const TPickArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToPick: TPickArray\n) => {\n\tconst keysToPickSet = new Set(keysToPick);\n\n\tconst arrayFromInitObject = Object.entries(initialObject);\n\n\tconst filteredArray = arrayFromInitObject.filter(([objectKey]) => keysToPickSet.has(objectKey));\n\n\tconst updatedObject = Object.fromEntries(filteredArray);\n\n\treturn updatedObject as Pick<TObject, TPickArray[number]>;\n};\n\nexport const splitConfig = <TObject extends object>(\n\tconfig: TObject\n): [\"body\" extends keyof TObject ? $RequestConfig : $BaseRequestConfig, ExtraOptions] => [\n\tpickKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n\tomitKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n];\n\nexport const handleResponseType = <TResponse>(\n\tresponse: Response,\n\tparser?: Required<ExtraOptions>[\"responseParser\"]\n) => ({\n\tjson: async () => {\n\t\tif (parser) {\n\t\t\treturn parser<TResponse>(await response.text());\n\t\t}\n\n\t\treturn response.json() as Promise<TResponse>;\n\t},\n\tarrayBuffer: () => response.arrayBuffer() as Promise<TResponse>,\n\tblob: () => response.blob() as Promise<TResponse>,\n\tformData: () => response.formData() as Promise<TResponse>,\n\ttext: () => response.text() as Promise<TResponse>,\n});\n\nexport const getResponseData = <TResponse>(\n\tresponse: Response,\n\tresponseType: keyof ReturnType<typeof handleResponseType>,\n\tparser: ExtraOptions[\"responseParser\"]\n) => {\n\tconst RESPONSE_TYPE_LOOKUP = handleResponseType<TResponse>(response, parser);\n\n\tif (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, responseType)) {\n\t\tthrow new Error(`Invalid response type: ${responseType}`);\n\t}\n\n\treturn RESPONSE_TYPE_LOOKUP[responseType]();\n};\n\ntype DataInfo = {\n\tsuccessData: unknown;\n\toptions: ExtraOptions;\n\tresponse: Response;\n};\n\n// == The CallApiResult type is used to cast all return statements due to a design limitation in ts.\n// LINK - See https://www.zhenghao.io/posts/type-functions for more info\nexport const resolveSuccessResult = <CallApiResult>(info: DataInfo): CallApiResult => {\n\tconst { options, response, successData } = info;\n\n\tconst apiDetails = { dataInfo: successData, errorInfo: null, response };\n\n\tif (options.resultMode === undefined || options.resultMode === \"all\") {\n\t\treturn apiDetails as CallApiResult;\n\t}\n\n\treturn {\n\t\tonlySuccess: apiDetails.dataInfo,\n\t\tonlyError: apiDetails.errorInfo,\n\t\tonlyResponse: apiDetails.response,\n\t}[options.resultMode] as CallApiResult;\n};\n\n// == Using curring here so error and options are not required to be passed every time, instead to be captured once by way of closure\nexport const $resolveErrorResult = <CallApiResult>($info: { error?: unknown; options: ExtraOptions }) => {\n\tconst { error, options } = $info;\n\n\ttype ErrorInfo = {\n\t\tresponse?: Response;\n\t\terrorData?: unknown;\n\t\tmessage?: string;\n\t};\n\n\tconst resolveErrorResult = (info: ErrorInfo = {}): CallApiResult => {\n\t\tconst { errorData, message, response } = info;\n\n\t\tconst shouldThrowOnError = isFunction(options.throwOnError)\n\t\t\t? options.throwOnError(error as Error)\n\t\t\t: options.throwOnError;\n\n\t\tif (shouldThrowOnError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\treturn {\n\t\t\tdataInfo: null,\n\t\t\terrorInfo: {\n\t\t\t\terrorName: (error as PossibleErrorObject)?.name ?? \"UnknownError\",\n\t\t\t\tmessage: message ?? (error as PossibleErrorObject)?.message ?? options.defaultErrorMessage,\n\t\t\t\t...(Boolean(errorData) && { errorData }),\n\t\t\t},\n\t\t\tresponse: response ?? null,\n\t\t} as CallApiResult;\n\t};\n\n\treturn resolveErrorResult;\n};\nexport const isHTTPErrorInfo = (\n\terrorInfo: Record<string, unknown> | null\n): errorInfo is { errorName: \"HTTPError\" } => isObject(errorInfo) && errorInfo.errorName === \"HTTPError\";\n\ntype ErrorDetails<TErrorResponse> = {\n\tresponse: Response & { errorData: TErrorResponse };\n\tdefaultErrorMessage: string;\n};\n\ntype ErrorOptions = {\n\tcause?: unknown;\n};\n\nexport class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {\n\tresponse: ErrorDetails<TErrorResponse>[\"response\"];\n\n\toverride name = \"HTTPError\" as const;\n\n\tisHTTPError = true;\n\n\tconstructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions) {\n\t\tconst { defaultErrorMessage, response } = errorDetails;\n\n\t\tsuper((response.errorData as { message?: string }).message ?? defaultErrorMessage, errorOptions);\n\n\t\tthis.response = response;\n\t}\n}\n\nexport const isHTTPErrorInstance = <TErrorResponse>(\n\terror: unknown\n): error is HTTPError<TErrorResponse> => {\n\treturn (\n\t\terror instanceof HTTPError ||\n\t\t(isObject(error) && error.name === \"HTTPError\" && error.isHTTPError === true)\n\t);\n};\n\nexport const wait = (delay: number) => {\n\tif (delay === 0) return;\n\n\tconst { promise, resolve } = Promise.withResolvers();\n\n\tsetTimeout(resolve, delay);\n\n\treturn promise;\n};\n\nconst isArray = <TArray>(value: unknown): value is TArray[] => Array.isArray(value);\n\nexport const isFormData = (value: unknown) => value instanceof FormData;\n\nexport const isObject = <TObject extends Record<string, unknown>>(value: unknown): value is TObject => {\n\treturn typeof value === \"object\" && value !== null && !isFormData(value) && !Array.isArray(value);\n};\n\nexport const isFunction = <TFunction extends AnyFunction>(value: unknown): value is TFunction =>\n\ttypeof value === \"function\";\n"]}
|
package/dist/utils.d.cts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { $ as $resolveErrorResult, H as HTTPError, d as defaultRetryCodes, b as defaultRetryMethods, f as fetchSpecificKeys, g as getResponseData, h as handleResponseType, c as isFormData, j as isFunction, i as isHTTPErrorInfo, a as isHTTPErrorInstance, e as isObject, m as mergeUrlWithParams, o as objectifyHeaders, r as resolveSuccessResult, s as splitConfig, w as wait } from './types-Cf2V09St.cjs';
|
package/dist/utils.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { $ as $resolveErrorResult, H as HTTPError, d as defaultRetryCodes, b as defaultRetryMethods, f as fetchSpecificKeys, g as getResponseData, h as handleResponseType, c as isFormData, j as isFunction, i as isHTTPErrorInfo, a as isHTTPErrorInstance, e as isObject, m as mergeUrlWithParams, o as objectifyHeaders, r as resolveSuccessResult, s as splitConfig, w as wait } from './types-Cf2V09St.js';
|
package/dist/utils.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
const m=(e,r)=>{if(!r)return e;const o=new URLSearchParams(r).toString();return e.includes("?")&&!e.endsWith("?")?`${e}&${o}`:e.endsWith("?")?`${e}${o}`:`${e}?${o}`},b=e=>!e||i(e)?e:Object.fromEntries(d(e)?e:e.entries()),p={408:"Request Timeout",409:"Conflict",425:"Too Early",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},g=Object.keys(p).map(Number),j=["GET"],c=["body","integrity","method","headers","signal","cache","redirect","window","credentials","keepalive","referrer","priority","mode","referrerPolicy"],l=(e,r)=>{const o=Object.entries(e).filter(([s])=>!r.includes(s));return Object.fromEntries(o)},T=(e,r)=>{const o=new Set(r),s=Object.entries(e).filter(([a])=>o.has(a));return Object.fromEntries(s)},x=e=>[T(e,c),l(e,c)],f=(e,r)=>({json:async()=>r?r(await e.text()):e.json(),arrayBuffer:()=>e.arrayBuffer(),blob:()=>e.blob(),formData:()=>e.formData(),text:()=>e.text()}),w=(e,r,o)=>{const t=f(e,o);if(!Object.hasOwn(t,r))throw new Error(`Invalid response type: ${r}`);return t[r]()},k=e=>{const{options:r,response:o,successData:t}=e,s={dataInfo:t,errorInfo:null,response:o};return r.resultMode===void 0||r.resultMode==="all"?s:{onlySuccess:s.dataInfo,onlyError:s.errorInfo,onlyResponse:s.response}[r.resultMode]},P=e=>{const{error:r,options:o}=e;return (s={})=>{const{errorData:n,message:a,response:u}=s;if(E(o.throwOnError)?o.throwOnError(r):o.throwOnError)throw r;return {dataInfo:null,errorInfo:{errorName:r?.name??"UnknownError",message:a??r?.message??o.defaultErrorMessage,...!!n&&{errorData:n}},response:u??null}}},A=e=>i(e)&&e.errorName==="HTTPError";class y extends Error{response;name="HTTPError";isHTTPError=!0;constructor(r,o){const{defaultErrorMessage:t,response:s}=r;super(s.errorData.message??t,o),this.response=s;}}const h=e=>e instanceof y||i(e)&&e.name==="HTTPError"&&e.isHTTPError===!0,C=e=>{if(e===0)return;const{promise:r,resolve:o}=Promise.withResolvers();return setTimeout(o,e),r},d=e=>Array.isArray(e),R=e=>e instanceof FormData,i=e=>typeof e=="object"&&e!==null&&!R(e)&&!Array.isArray(e),E=e=>typeof e=="function";
|
|
2
|
-
|
|
3
|
-
export { P as $resolveErrorResult, y as HTTPError, g as defaultRetryCodes, j as defaultRetryMethods, c as fetchSpecificKeys, w as getResponseData, f as handleResponseType, R as isFormData, E as isFunction, A as isHTTPErrorInfo, h as isHTTPErrorInstance, i as isObject, m as mergeUrlWithParams, b as objectifyHeaders, k as resolveSuccessResult, x as splitConfig, C as wait };
|
|
4
|
-
//# sourceMappingURL=out.js.map
|
|
5
|
-
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils.ts"],"names":["mergeUrlWithParams","url","params","paramsString","objectifyHeaders","headers","isObject","isArray","retryCodesLookup","defaultRetryCodes","defaultRetryMethods","fetchSpecificKeys","omitKeys","initialObject","keysToOmit","arrayFromFilteredObject","key","pickKeys","keysToPick","keysToPickSet","filteredArray","objectKey","splitConfig","config","handleResponseType","response","parser","getResponseData","responseType","RESPONSE_TYPE_LOOKUP","resolveSuccessResult","info","options","successData","apiDetails","$resolveErrorResult","$info","error","errorData","message","isFunction","isHTTPErrorInfo","errorInfo","HTTPError","errorDetails","errorOptions","defaultErrorMessage","isHTTPErrorInstance","wait","delay","promise","resolve","value","isFormData"],"mappings":"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","sourcesContent":["import type { AnyFunction } from \"./type-helpers\";\nimport type {\n\t$BaseRequestConfig,\n\t$RequestConfig,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tPossibleErrorObject,\n} from \"./types\";\n\nexport const mergeUrlWithParams = (url: string, params: ExtraOptions[\"query\"]): string => {\n\tif (!params) {\n\t\treturn url;\n\t}\n\n\tconst paramsString = new URLSearchParams(params as Record<string, string>).toString();\n\n\tif (url.includes(\"?\") && !url.endsWith(\"?\")) {\n\t\treturn `${url}&${paramsString}`;\n\t}\n\n\tif (url.endsWith(\"?\")) {\n\t\treturn `${url}${paramsString}`;\n\t}\n\n\treturn `${url}?${paramsString}`;\n};\n\nexport const objectifyHeaders = (headers: RequestInit[\"headers\"]): Record<string, string> | undefined => {\n\tif (!headers || isObject(headers)) {\n\t\treturn headers;\n\t}\n\n\treturn Object.fromEntries(isArray(headers) ? headers : headers.entries());\n};\n\nconst retryCodesLookup = {\n\t408: \"Request Timeout\",\n\t409: \"Conflict\",\n\t425: \"Too Early\",\n\t429: \"Too Many Requests\",\n\t500: \"Internal Server Error\",\n\t502: \"Bad Gateway\",\n\t503: \"Service Unavailable\",\n\t504: \"Gateway Timeout\",\n};\n\nexport const defaultRetryCodes: Required<BaseConfig>[\"retryCodes\"] =\n\tObject.keys(retryCodesLookup).map(Number);\n\nexport const defaultRetryMethods: Required<BaseConfig>[\"retryMethods\"] = [\"GET\"];\n\nexport const fetchSpecificKeys = [\n\t\"body\",\n\t\"integrity\",\n\t\"method\",\n\t\"headers\",\n\t\"signal\",\n\t\"cache\",\n\t\"redirect\",\n\t\"window\",\n\t\"credentials\",\n\t\"keepalive\",\n\t\"referrer\",\n\t\"priority\",\n\t\"mode\",\n\t\"referrerPolicy\",\n] satisfies Array<keyof FetchConfig>;\n\nconst omitKeys = <TObject extends Record<string, unknown>, const TOmitArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToOmit: TOmitArray\n) => {\n\tconst arrayFromFilteredObject = Object.entries(initialObject).filter(\n\t\t([key]) => !keysToOmit.includes(key)\n\t);\n\n\tconst updatedObject = Object.fromEntries(arrayFromFilteredObject);\n\n\treturn updatedObject as Omit<TObject, keyof TOmitArray>;\n};\n\nconst pickKeys = <TObject extends Record<string, unknown>, const TPickArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToPick: TPickArray\n) => {\n\tconst keysToPickSet = new Set(keysToPick);\n\n\tconst arrayFromInitObject = Object.entries(initialObject);\n\n\tconst filteredArray = arrayFromInitObject.filter(([objectKey]) => keysToPickSet.has(objectKey));\n\n\tconst updatedObject = Object.fromEntries(filteredArray);\n\n\treturn updatedObject as Pick<TObject, TPickArray[number]>;\n};\n\nexport const splitConfig = <TObject extends object>(\n\tconfig: TObject\n): [\"body\" extends keyof TObject ? $RequestConfig : $BaseRequestConfig, ExtraOptions] => [\n\tpickKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n\tomitKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n];\n\nexport const handleResponseType = <TResponse>(\n\tresponse: Response,\n\tparser?: Required<ExtraOptions>[\"responseParser\"]\n) => ({\n\tjson: async () => {\n\t\tif (parser) {\n\t\t\treturn parser<TResponse>(await response.text());\n\t\t}\n\n\t\treturn response.json() as Promise<TResponse>;\n\t},\n\tarrayBuffer: () => response.arrayBuffer() as Promise<TResponse>,\n\tblob: () => response.blob() as Promise<TResponse>,\n\tformData: () => response.formData() as Promise<TResponse>,\n\ttext: () => response.text() as Promise<TResponse>,\n});\n\nexport const getResponseData = <TResponse>(\n\tresponse: Response,\n\tresponseType: keyof ReturnType<typeof handleResponseType>,\n\tparser: ExtraOptions[\"responseParser\"]\n) => {\n\tconst RESPONSE_TYPE_LOOKUP = handleResponseType<TResponse>(response, parser);\n\n\tif (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, responseType)) {\n\t\tthrow new Error(`Invalid response type: ${responseType}`);\n\t}\n\n\treturn RESPONSE_TYPE_LOOKUP[responseType]();\n};\n\ntype DataInfo = {\n\tsuccessData: unknown;\n\toptions: ExtraOptions;\n\tresponse: Response;\n};\n\n// == The CallApiResult type is used to cast all return statements due to a design limitation in ts.\n// LINK - See https://www.zhenghao.io/posts/type-functions for more info\nexport const resolveSuccessResult = <CallApiResult>(info: DataInfo): CallApiResult => {\n\tconst { options, response, successData } = info;\n\n\tconst apiDetails = { dataInfo: successData, errorInfo: null, response };\n\n\tif (options.resultMode === undefined || options.resultMode === \"all\") {\n\t\treturn apiDetails as CallApiResult;\n\t}\n\n\treturn {\n\t\tonlySuccess: apiDetails.dataInfo,\n\t\tonlyError: apiDetails.errorInfo,\n\t\tonlyResponse: apiDetails.response,\n\t}[options.resultMode] as CallApiResult;\n};\n\n// == Using curring here so error and options are not required to be passed every time, instead to be captured once by way of closure\nexport const $resolveErrorResult = <CallApiResult>($info: { error?: unknown; options: ExtraOptions }) => {\n\tconst { error, options } = $info;\n\n\ttype ErrorInfo = {\n\t\tresponse?: Response;\n\t\terrorData?: unknown;\n\t\tmessage?: string;\n\t};\n\n\tconst resolveErrorResult = (info: ErrorInfo = {}): CallApiResult => {\n\t\tconst { errorData, message, response } = info;\n\n\t\tconst shouldThrowOnError = isFunction(options.throwOnError)\n\t\t\t? options.throwOnError(error as Error)\n\t\t\t: options.throwOnError;\n\n\t\tif (shouldThrowOnError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\treturn {\n\t\t\tdataInfo: null,\n\t\t\terrorInfo: {\n\t\t\t\terrorName: (error as PossibleErrorObject)?.name ?? \"UnknownError\",\n\t\t\t\tmessage: message ?? (error as PossibleErrorObject)?.message ?? options.defaultErrorMessage,\n\t\t\t\t...(Boolean(errorData) && { errorData }),\n\t\t\t},\n\t\t\tresponse: response ?? null,\n\t\t} as CallApiResult;\n\t};\n\n\treturn resolveErrorResult;\n};\nexport const isHTTPErrorInfo = (\n\terrorInfo: Record<string, unknown> | null\n): errorInfo is { errorName: \"HTTPError\" } => isObject(errorInfo) && errorInfo.errorName === \"HTTPError\";\n\ntype ErrorDetails<TErrorResponse> = {\n\tresponse: Response & { errorData: TErrorResponse };\n\tdefaultErrorMessage: string;\n};\n\ntype ErrorOptions = {\n\tcause?: unknown;\n};\n\nexport class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {\n\tresponse: ErrorDetails<TErrorResponse>[\"response\"];\n\n\toverride name = \"HTTPError\" as const;\n\n\tisHTTPError = true;\n\n\tconstructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions) {\n\t\tconst { defaultErrorMessage, response } = errorDetails;\n\n\t\tsuper((response.errorData as { message?: string }).message ?? defaultErrorMessage, errorOptions);\n\n\t\tthis.response = response;\n\t}\n}\n\nexport const isHTTPErrorInstance = <TErrorResponse>(\n\terror: unknown\n): error is HTTPError<TErrorResponse> => {\n\treturn (\n\t\terror instanceof HTTPError ||\n\t\t(isObject(error) && error.name === \"HTTPError\" && error.isHTTPError === true)\n\t);\n};\n\nexport const wait = (delay: number) => {\n\tif (delay === 0) return;\n\n\tconst { promise, resolve } = Promise.withResolvers();\n\n\tsetTimeout(resolve, delay);\n\n\treturn promise;\n};\n\nconst isArray = <TArray>(value: unknown): value is TArray[] => Array.isArray(value);\n\nexport const isFormData = (value: unknown) => value instanceof FormData;\n\nexport const isObject = <TObject extends Record<string, unknown>>(value: unknown): value is TObject => {\n\treturn typeof value === \"object\" && value !== null && !isFormData(value) && !Array.isArray(value);\n};\n\nexport const isFunction = <TFunction extends AnyFunction>(value: unknown): value is TFunction =>\n\ttypeof value === \"function\";\n"]}
|