-
Notifications
You must be signed in to change notification settings - Fork 52
/
types.ts
172 lines (145 loc) · 3.8 KB
/
types.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
export type Method =
| 'get'
| 'post'
| 'put'
| 'patch'
| 'delete'
| 'head'
| 'options'
export type OpenapiPaths<Paths> = {
[P in keyof Paths]: {
[M in Method]?: unknown
}
}
export type OpArgType<OP> = OP extends {
parameters?: {
path?: infer P
query?: infer Q
body?: infer B
header?: unknown // ignore
cookie?: unknown // ignore
}
// openapi 3
requestBody?: {
content: {
'application/json': infer RB
}
}
}
? P & Q & (B extends Record<string, unknown> ? B[keyof B] : unknown) & RB
: Record<string, never>
type OpResponseTypes<OP> = OP extends {
responses: infer R
}
? {
[S in keyof R]: R[S] extends { schema?: infer S } // openapi 2
? S
: R[S] extends { content: { 'application/json': infer C } } // openapi 3
? C
: S extends 'default'
? R[S]
: unknown
}
: never
type _OpReturnType<T> = 200 extends keyof T
? T[200]
: 201 extends keyof T
? T[201]
: 'default' extends keyof T
? T['default']
: unknown
export type OpReturnType<OP> = _OpReturnType<OpResponseTypes<OP>>
type _OpDefaultReturnType<T> = 'default' extends keyof T
? T['default']
: unknown
export type OpDefaultReturnType<OP> = _OpDefaultReturnType<OpResponseTypes<OP>>
// private symbol to prevent narrowing on "default" error status
const never: unique symbol = Symbol()
type _OpErrorType<T> = {
[S in Exclude<keyof T, 200 | 201>]: {
status: S extends 'default' ? typeof never : S
data: T[S]
}
}[Exclude<keyof T, 200 | 201>]
type Coalesce<T, D> = [T] extends [never] ? D : T
// coalesce default error type
export type OpErrorType<OP> = Coalesce<
_OpErrorType<OpResponseTypes<OP>>,
{ status: number; data: any }
>
export type CustomRequestInit = Omit<RequestInit, 'headers'> & {
readonly headers: Headers
}
export type Fetch = (
url: string,
init: CustomRequestInit,
) => Promise<ApiResponse>
export type _TypedFetch<OP> = (
arg: OpArgType<OP>,
init?: RequestInit,
) => Promise<ApiResponse<OpReturnType<OP>>>
export type TypedFetch<OP> = _TypedFetch<OP> & {
Error: new (error: ApiError) => ApiError & {
getActualType: () => OpErrorType<OP>
}
}
export type FetchArgType<F> = F extends TypedFetch<infer OP>
? OpArgType<OP>
: never
export type FetchReturnType<F> = F extends TypedFetch<infer OP>
? OpReturnType<OP>
: never
export type FetchErrorType<F> = F extends TypedFetch<infer OP>
? OpErrorType<OP>
: never
type _CreateFetch<OP, Q = never> = [Q] extends [never]
? () => TypedFetch<OP>
: (query: Q) => TypedFetch<OP>
export type CreateFetch<M, OP> = M extends 'post' | 'put' | 'patch' | 'delete'
? OP extends { parameters: { query: infer Q } }
? _CreateFetch<OP, { [K in keyof Q]: true | 1 }>
: _CreateFetch<OP>
: _CreateFetch<OP>
export type Middleware = (
url: string,
init: CustomRequestInit,
next: Fetch,
) => Promise<ApiResponse>
export type FetchConfig = {
baseUrl?: string
init?: RequestInit
use?: Middleware[]
}
export type Request = {
baseUrl: string
method: Method
path: string
queryParams: string[] // even if a post these will be sent in query
payload: any
init?: RequestInit
fetch: Fetch
}
export type ApiResponse<R = any> = {
readonly headers: Headers
readonly url: string
readonly ok: boolean
readonly status: number
readonly statusText: string
readonly data: R
}
export class ApiError extends Error {
readonly headers: Headers
readonly url: string
readonly status: number
readonly statusText: string
readonly data: any
constructor(response: Omit<ApiResponse, 'ok'>) {
super(response.statusText)
Object.setPrototypeOf(this, new.target.prototype)
this.headers = response.headers
this.url = response.url
this.status = response.status
this.statusText = response.statusText
this.data = response.data
}
}