1000fetches 0.2.0 → 0.2.2

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/docs/API.md ADDED
@@ -0,0 +1,754 @@
1
+ # 1000fetches API Documentation
2
+
3
+ ## Table of Contents
4
+
5
+ - [HttpClient](#httpclient)
6
+ - [Configuration Options](#configuration-options)
7
+ - [HTTP Methods](#http-methods)
8
+ - [Response Methods](#response-methods)
9
+ - [Schema Validation](#schema-validation)
10
+ - [Error Handling](#error-handling)
11
+ - [Middleware](#middleware)
12
+ - [Retry Logic](#retry-logic)
13
+ - [Type Safety](#type-safety)
14
+
15
+ ## HttpClient
16
+
17
+ The main HTTP client that provides a fluent API for making HTTP requests with optional schema validation.
18
+
19
+ ### Constructor
20
+
21
+ ```typescript
22
+ createHttpClient(config?: HttpClientConfig)
23
+ ```
24
+
25
+ **Parameters:**
26
+
27
+ - `config` (optional): Configuration options for the HTTP client
28
+
29
+ **Example:**
30
+
31
+ ```typescript
32
+ import { createHttpClient } from '1000fetches'
33
+
34
+ const client = createHttpClient({
35
+ baseUrl: 'https://api.example.com',
36
+ timeout: 5_000,
37
+ headers: {
38
+ Authorization: 'Bearer your-token',
39
+ },
40
+ })
41
+
42
+ // Or use the shortcut instance
43
+ import http from '1000fetches/http'
44
+ ```
45
+
46
+ `1000fetches/http` intentionally exposes only a default export. Use the root `1000fetches` entrypoint for `createHttpClient`, error classes, and public types.
47
+
48
+ ## Configuration Options
49
+
50
+ ### HttpClientConfig
51
+
52
+ ```typescript
53
+ interface HttpClientConfig {
54
+ baseUrl?: string
55
+ timeout?: number
56
+ headers?: HttpHeaders
57
+ retry?: RetryOptions | boolean
58
+ schemaValidator?: SchemaValidator
59
+ fetch?: CustomFetch
60
+ serializeBody?: SerializeBody
61
+ serializeParams?: SerializeParams
62
+ onRequestMiddleware?: <TBody = unknown>(
63
+ context: RequestContext<TBody>
64
+ ) => RequestContext<TBody> | Promise<RequestContext<TBody>>
65
+ onResponseMiddleware?: (
66
+ response: ResponseType<unknown>
67
+ ) => ResponseType<unknown> | Promise<ResponseType<unknown>>
68
+ }
69
+ ```
70
+
71
+ **Properties:**
72
+
73
+ - **`baseUrl`** (optional): Base URL for all requests. Must be an absolute URL or a root-relative path starting with `/`. Native Node.js `fetch` still requires absolute URLs unless you provide a custom `fetch` implementation.
74
+ - **`timeout`** (optional): Request timeout in milliseconds (default: 30_000)
75
+ - **`headers`** (optional): Default headers to include with every request
76
+ - **`retry`** (optional): Opt-in retry configuration for failed requests. Use `true` for the default retry policy, `false` to disable retry, or a `RetryOptions` object for custom retry behavior.
77
+ - **`schemaValidator`** (optional): Custom schema validator
78
+ - **`fetch`** (optional): Custom fetch implementation
79
+ - **`serializeBody`** (optional): Custom body serializer
80
+ - **`serializeParams`** (optional): Custom params serializer
81
+ - **`onRequestMiddleware`** (optional): Request middleware for modifying requests
82
+ - **`onResponseMiddleware`** (optional): Response middleware for modifying responses
83
+
84
+ ### RetryOptions
85
+
86
+ ```typescript
87
+ interface RetryOptions {
88
+ maxRetries?: number
89
+ retryDelay?: number
90
+ backoffFactor?: number
91
+ retryStatusCodes?: number[]
92
+ retryNetworkErrors?: boolean
93
+ maxRetryDelay?: number
94
+ retryMethods?: readonly HttpMethod[]
95
+ retryUnsafeMethods?: boolean
96
+ jitter?:
97
+ | 'none'
98
+ | 'full'
99
+ | 'equal'
100
+ | ((delay: number, retryCount: number) => number)
101
+ respectRetryAfter?: boolean
102
+ onRetry?: (event: RetryEvent) => void | Promise<void>
103
+ shouldRetry?: (
104
+ error: Error,
105
+ retryCount: number,
106
+ context: RetryContext
107
+ ) => boolean | Promise<boolean>
108
+ }
109
+ ```
110
+
111
+ Retries are disabled by default. Set client `retry`, request `retry: true`, or per-request retry settings to enable them. The built-in policy retries idempotent methods by default and skips one-shot stream bodies.
112
+
113
+ **Properties:**
114
+
115
+ - **`maxRetries`**: Maximum number of retry attempts when retries are enabled (default: 3)
116
+ - **`retryDelay`**: Initial delay between retries in milliseconds when retries are enabled (default: 300)
117
+ - **`backoffFactor`**: Exponential backoff multiplier (default: 2)
118
+ - **`retryStatusCodes`**: HTTP status codes that trigger retries when retries are enabled (default: [408, 429, 500, 502, 503, 504])
119
+ - **`retryNetworkErrors`**: Whether to retry on network errors when retries are enabled (default: true)
120
+ - **`maxRetryDelay`**: Maximum delay between retries when retries are enabled (default: 30_000)
121
+ - **`retryMethods`**: Methods eligible for built-in retries (default: GET, HEAD, OPTIONS, PUT, DELETE)
122
+ - **`retryUnsafeMethods`**: Allow built-in retries for methods outside `retryMethods` (default: false)
123
+ - **`jitter`**: Optional jitter strategy for exponential backoff delays (default: 'none')
124
+ - **`respectRetryAfter`**: Honor `Retry-After` headers on HTTP errors (default: true)
125
+ - **`onRetry`**: Hook called before waiting for the next attempt
126
+ - **`shouldRetry`**: Custom function to determine if a request should be retried
127
+
128
+ ## HTTP Methods
129
+
130
+ All HTTP methods return a response object that allows you to optionally attach schema validation.
131
+
132
+ ### GET Request
133
+
134
+ ```typescript
135
+ client.get<Path, TParams>(url: Path, options?: EnforcedPathParamsOptions<never, TParams, Path>): SchemaableResponse<unknown>
136
+ ```
137
+
138
+ **Example:**
139
+
140
+ ```typescript
141
+ // Basic GET request - returns unknown data
142
+ const users = await client.get('/users')
143
+ // users.data 👉 unknown
144
+
145
+ // GET with path parameters
146
+ const user = await client.get('/users/:id', {
147
+ pathParams: { id: '123' },
148
+ })
149
+
150
+ // GET with query parameters
151
+ const filteredUsers = await client.get('/users', {
152
+ params: {
153
+ page: 1,
154
+ limit: 10,
155
+ status: 'active',
156
+ },
157
+ })
158
+
159
+ // GET with schema validation
160
+ import { z } from 'zod'
161
+
162
+ const userSchema = z.object({
163
+ id: z.number(),
164
+ name: z.string(),
165
+ email: z.string(),
166
+ })
167
+
168
+ const validatedUser = await client.get('/users/1').schema(userSchema)
169
+ // validatedUser.data 👉 { id: number, name: string, email: string }
170
+ ```
171
+
172
+ ### POST Request
173
+
174
+ ```typescript
175
+ client.post<Path, TBody, TParams>(url: Path, body?: TBody, options?: EnforcedPathParamsOptions<TBody, TParams, Path>): SchemaableResponse<unknown>
176
+ ```
177
+
178
+ **Example:**
179
+
180
+ ```typescript
181
+ // Basic POST request - returns unknown data
182
+ const newUser = await client.post('/users', {
183
+ name: 'John Doe',
184
+ email: 'john@example.com',
185
+ })
186
+
187
+ // POST with schema
188
+ const createUserSchema = z.object({
189
+ name: z.string(),
190
+ email: z.string(),
191
+ })
192
+
193
+ const responseSchema = z.object({
194
+ id: z.number(),
195
+ name: z.string(),
196
+ email: z.string(),
197
+ createdAt: z.string(),
198
+ })
199
+
200
+ const validatedUser = await client
201
+ .post('/users', userData)
202
+ .schema(responseSchema)
203
+ // validatedUser.data 👉 { id: number, name: string, email: string, createdAt: string }
204
+ ```
205
+
206
+ ### PUT Request
207
+
208
+ ```typescript
209
+ client.put<Path, TBody, TParams>(url: Path, body?: TBody, options?: EnforcedPathParamsOptions<TBody, TParams, Path>): SchemaableResponse<unknown>
210
+ ```
211
+
212
+ ### PATCH Request
213
+
214
+ ```typescript
215
+ client.patch<Path, TBody, TParams>(url: Path, body?: TBody, options?: EnforcedPathParamsOptions<TBody, TParams, Path>): SchemaableResponse<unknown>
216
+ ```
217
+
218
+ ### DELETE Request
219
+
220
+ ```typescript
221
+ client.delete<Path, TParams>(url: Path, options?: EnforcedPathParamsOptions<never, TParams, Path>): SchemaableResponse<unknown>
222
+ ```
223
+
224
+ ### Generic Request
225
+
226
+ ```typescript
227
+ client.request<Path, TBody, TParams>(url: Path, options?: HttpRequestOptions<TBody, TParams>): SchemaableResponse<unknown>
228
+ ```
229
+
230
+ **Example:**
231
+
232
+ ```typescript
233
+ // HEAD request
234
+ const headResponse = await client.request('/users/1', { method: 'HEAD' })
235
+
236
+ // OPTIONS request
237
+ const optionsResponse = await client.request('/users', { method: 'OPTIONS' })
238
+ ```
239
+
240
+ ## Response Methods
241
+
242
+ All HTTP methods return a `SchemaableResponse<unknown>` that provides extraction helpers:
243
+
244
+ ### SchemaableResponse Interface
245
+
246
+ ```typescript
247
+ interface SchemaableResponse<T> extends Promise<ResponseType<T>> {
248
+ schema<S extends Schema>(schema: S): ExtractableResponse<InferSchemaOutput<S>>
249
+ data(): Promise<T>
250
+ void(): Promise<void>
251
+ }
252
+ ```
253
+
254
+ ### Methods
255
+
256
+ #### `.schema(schema)`
257
+
258
+ Validates the response data against a schema and returns a typed response.
259
+
260
+ ```typescript
261
+ const response = await client.get('/users/1').schema(userSchema)
262
+ // awaited response 👉 ResponseType<User>
263
+ // response.data 👉 User
264
+ ```
265
+
266
+ #### `.data()`
267
+
268
+ Extracts the data from the response without schema validation.
269
+
270
+ ```typescript
271
+ const data = await client.get('/users/1').data()
272
+ // data 👉 unknown
273
+ ```
274
+
275
+ #### `.void()`
276
+
277
+ Runs the request and resolves without returning response data. Errors and schema validation still run.
278
+
279
+ ```typescript
280
+ await client.delete('/users/1').void()
281
+ ```
282
+
283
+ ### Design Philosophy
284
+
285
+ 1. **Fetch unknown data once** - All requests return `unknown` data by default
286
+ 2. **Schema changes type** - If `.schema()` is called, it validates and types the data
287
+ 3. **No schema = unknown data** - If no schema is provided, data remains `unknown`
288
+ 4. **Single HTTP request** - Schema validation happens client-side, no duplicate requests
289
+
290
+ ## Error Handling
291
+
292
+ ### Error Types
293
+
294
+ The library provides structured error types for different failure scenarios:
295
+
296
+ #### HttpError
297
+
298
+ Thrown when an HTTP request fails with a non-2xx status code.
299
+
300
+ ```typescript
301
+ class HttpError extends Error {
302
+ readonly status: number
303
+ readonly statusText: string
304
+ readonly data: unknown
305
+ readonly response: Response
306
+ readonly url: string
307
+ readonly method: string
308
+ }
309
+ ```
310
+
311
+ #### NetworkError
312
+
313
+ Thrown when a network-level error occurs.
314
+
315
+ ```typescript
316
+ class NetworkError extends Error {
317
+ readonly cause?: Error
318
+ }
319
+ ```
320
+
321
+ #### TimeoutError
322
+
323
+ Thrown when a request exceeds the timeout limit.
324
+
325
+ ```typescript
326
+ class TimeoutError extends Error {
327
+ readonly cause?: Error
328
+ }
329
+ ```
330
+
331
+ #### SchemaValidationError
332
+
333
+ Thrown when response data fails schema validation.
334
+
335
+ ```typescript
336
+ class SchemaValidationError extends Error {
337
+ readonly schema: unknown
338
+ readonly data: unknown
339
+ readonly issues?: ReadonlyArray<StandardSchemaV1.Issue>
340
+ readonly cause?: Error
341
+ }
342
+ ```
343
+
344
+ #### MiddlewareError
345
+
346
+ Thrown when middleware fails.
347
+
348
+ ```typescript
349
+ class MiddlewareError extends Error {
350
+ readonly type: 'request' | 'response'
351
+ readonly url?: string
352
+ readonly method?: string
353
+ readonly cause?: Error
354
+ }
355
+ ```
356
+
357
+ #### PathParameterError
358
+
359
+ Thrown when required path parameters are missing.
360
+
361
+ ```typescript
362
+ class PathParameterError extends Error {
363
+ readonly url: string
364
+ readonly requiredParams: string[]
365
+ readonly providedParams: string[]
366
+ readonly cause?: Error
367
+ }
368
+ ```
369
+
370
+ #### SerializationError
371
+
372
+ Thrown when request serialization or successful-response parsing fails.
373
+
374
+ ```typescript
375
+ class SerializationError extends Error {
376
+ readonly cause?: Error
377
+ }
378
+ ```
379
+
380
+ #### InvalidSchemaError
381
+
382
+ Thrown when `.schema()` receives a value that does not implement Standard Schema v1.
383
+
384
+ ```typescript
385
+ class InvalidSchemaError extends Error {
386
+ readonly schema: unknown
387
+ readonly cause?: Error
388
+ }
389
+ ```
390
+
391
+ #### InvalidBaseUrlError
392
+
393
+ Thrown when `baseUrl` is not an absolute URL or root-relative path.
394
+
395
+ ```typescript
396
+ class InvalidBaseUrlError extends Error {
397
+ readonly baseUrl: string
398
+ readonly cause?: Error
399
+ }
400
+ ```
401
+
402
+ #### AsyncSchemaValidationError
403
+
404
+ Exported for custom schema integrations that need a structured async-schema validation error.
405
+
406
+ ```typescript
407
+ class AsyncSchemaValidationError extends Error {
408
+ readonly schema: unknown
409
+ readonly cause?: Error
410
+ }
411
+ ```
412
+
413
+ ### Error Handling Examples
414
+
415
+ ```typescript
416
+ import {
417
+ HttpError,
418
+ NetworkError,
419
+ TimeoutError,
420
+ SchemaValidationError,
421
+ } from '1000fetches'
422
+
423
+ try {
424
+ await client.get('/users/1')
425
+ } catch (error) {
426
+ if (error instanceof HttpError) {
427
+ console.log(`HTTP Error: ${error.status} ${error.statusText}`)
428
+ console.log(`Request: ${error.method} ${error.url}`)
429
+ console.log('Response data:', error.data)
430
+ } else if (error instanceof NetworkError) {
431
+ console.log('Network error occurred')
432
+ } else if (error instanceof TimeoutError) {
433
+ console.log('Request timed out')
434
+ } else if (error instanceof SchemaValidationError) {
435
+ console.log('Response data validation failed')
436
+ }
437
+ }
438
+ ```
439
+
440
+ ## Middleware
441
+
442
+ Middleware allows you to modify requests before they are sent and responses before they are returned.
443
+
444
+ ### Request Middleware
445
+
446
+ ```typescript
447
+ const client = createHttpClient({
448
+ onRequestMiddleware: async (context: RequestContext) => {
449
+ // Modify the request context
450
+ context.headers.set('X-Custom-Header', 'value')
451
+ return context
452
+ },
453
+ })
454
+ ```
455
+
456
+ ### Response Middleware
457
+
458
+ Response middleware receives the parsed response before HTTP status handling. The returned `ResponseType` is authoritative: if middleware changes `status`, that changed status controls whether the request resolves or throws `HttpError`, and it also affects retry decisions.
459
+
460
+ Use this deliberately for explicit recovery or status normalization. For example, a middleware can turn a `404` response into a successful `null` result:
461
+
462
+ ```typescript
463
+ const client = createHttpClient({
464
+ onResponseMiddleware: async response => {
465
+ if (response.status === 404) {
466
+ return {
467
+ ...response,
468
+ status: 200,
469
+ statusText: 'Recovered Not Found',
470
+ data: null,
471
+ }
472
+ }
473
+
474
+ return response
475
+ },
476
+ })
477
+
478
+ const user = await client.get('/users/missing').data()
479
+ // user 👉 null
480
+ ```
481
+
482
+ If middleware only needs to normalize error payloads, preserve `status` so the client still throws for real non-2xx responses:
483
+
484
+ ```typescript
485
+ const client = createHttpClient({
486
+ onResponseMiddleware: async (response: ResponseType<unknown>) => {
487
+ const data =
488
+ response.data && typeof response.data === 'object'
489
+ ? response.data
490
+ : { value: response.data }
491
+
492
+ return {
493
+ ...response,
494
+ data: {
495
+ ...data,
496
+ processed: true,
497
+ },
498
+ }
499
+ },
500
+ })
501
+ ```
502
+
503
+ ### Middleware Examples
504
+
505
+ ```typescript
506
+ // Authentication middleware
507
+ const client = createHttpClient({
508
+ onRequestMiddleware: async context => {
509
+ const token = await getAuthToken()
510
+ context.headers.set('Authorization', `Bearer ${token}`)
511
+ return context
512
+ },
513
+ })
514
+
515
+ // Logging middleware
516
+ const client = createHttpClient({
517
+ onRequestMiddleware: async context => {
518
+ console.log(`Making request: ${context.method} ${context.url}`)
519
+ return context
520
+ },
521
+ onResponseMiddleware: async response => {
522
+ console.log(`Response received: ${response.status} ${response.statusText}`)
523
+ return response
524
+ },
525
+ })
526
+
527
+ // Error handling middleware
528
+ const client = createHttpClient({
529
+ onResponseMiddleware: async response => {
530
+ if (response.status === 401) {
531
+ // Handle unauthorized
532
+ await refreshToken()
533
+ }
534
+ return response
535
+ },
536
+ })
537
+ ```
538
+
539
+ ## Schema Validation
540
+
541
+ The library supports schema validation using popular validation libraries through the Standard Schema interface. Schema validation is applied using the `.schema()` method.
542
+
543
+ ### Supported Libraries
544
+
545
+ - **Zod**
546
+ - **Valibot**
547
+ - **ArkType**
548
+ - **Custom validators**
549
+
550
+ ### Usage Examples
551
+
552
+ ```typescript
553
+ import { z } from 'zod'
554
+
555
+ // Define schemas
556
+ const userSchema = z.object({
557
+ id: z.number(),
558
+ name: z.string(),
559
+ email: z.string(),
560
+ age: z.number().optional(),
561
+ })
562
+
563
+ const createUserSchema = z.object({
564
+ name: z.string().min(1),
565
+ email: z.string(),
566
+ })
567
+
568
+ // Response validation
569
+ const user = await client.get('/users/1').schema(userSchema)
570
+ // user.data 👉 { id: number, name: string, email: string, age?: number }
571
+
572
+ // POST with response validation
573
+ const newUser = await client.post('/users', userData).schema(userSchema)
574
+ ```
575
+
576
+ ### Custom Schema Validator
577
+
578
+ ```typescript
579
+ import { createHttpClient } from '1000fetches'
580
+ import type { SchemaValidator } from '1000fetches'
581
+
582
+ const customValidator: SchemaValidator = {
583
+ async validate<T>(schema, data): Promise<T> {
584
+ const result = await schema['~standard'].validate(data)
585
+
586
+ if (result.issues) {
587
+ throw new Error(JSON.stringify(result.issues))
588
+ }
589
+
590
+ return result.value
591
+ },
592
+ }
593
+
594
+ const client = createHttpClient({
595
+ schemaValidator: customValidator,
596
+ })
597
+ ```
598
+
599
+ ## Retry Logic
600
+
601
+ The library includes built-in retry defaults with exponential backoff for handling transient failures when retries are enabled.
602
+
603
+ ### Retry Defaults When Enabled
604
+
605
+ ```typescript
606
+ const defaultRetryOptions = {
607
+ maxRetries: 3,
608
+ retryDelay: 300,
609
+ backoffFactor: 2,
610
+ retryStatusCodes: [408, 429, 500, 502, 503, 504],
611
+ retryNetworkErrors: true,
612
+ maxRetryDelay: 30_000,
613
+ retryMethods: ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'],
614
+ retryUnsafeMethods: false,
615
+ jitter: 'none',
616
+ respectRetryAfter: true,
617
+ }
618
+ ```
619
+
620
+ ### Custom Retry Configuration
621
+
622
+ ```typescript
623
+ import { NetworkError, createHttpClient } from '1000fetches'
624
+
625
+ const client = createHttpClient({
626
+ baseUrl: 'https://api.example.com',
627
+ retry: {
628
+ maxRetries: 5,
629
+ retryDelay: 1_000,
630
+ backoffFactor: 1.5,
631
+ retryStatusCodes: [500, 502, 503, 504],
632
+ jitter: 'full',
633
+ onRetry: event => {
634
+ console.log(event.method, event.url, event.nextAttempt)
635
+ },
636
+ shouldRetry: (error, retryCount, context) => {
637
+ // Custom retry logic
638
+ return (
639
+ retryCount < 3 &&
640
+ context.bodyReplayable &&
641
+ error instanceof NetworkError
642
+ )
643
+ },
644
+ },
645
+ })
646
+ ```
647
+
648
+ ### Per-Request Retry Configuration
649
+
650
+ ```typescript
651
+ // Disable retries for this request
652
+ await client.get('/users', { retry: false })
653
+
654
+ // Custom retry for this request
655
+ await client.get('/users', {
656
+ retry: {
657
+ maxRetries: 1,
658
+ retryDelay: 500,
659
+ },
660
+ })
661
+ ```
662
+
663
+ ## Type Safety
664
+
665
+ The library provides comprehensive TypeScript support with automatic type inference through response methods.
666
+
667
+ ### Response Method Types
668
+
669
+ ```typescript
670
+ interface User {
671
+ id: number
672
+ name: string
673
+ email: string
674
+ }
675
+
676
+ // Without schema - data is unknown
677
+ const response = await client.get('/users/1')
678
+ // response.data 👉 unknown
679
+
680
+ // With schema - data is typed
681
+ const user = await client.get('/users/1').schema(userSchema)
682
+ // user.data 👉 User
683
+
684
+ // Array responses
685
+ const users = await client.get('/users').schema(z.array(userSchema))
686
+ // users.data 👉 User[]
687
+ ```
688
+
689
+ ### Path Parameter Validation
690
+
691
+ ```typescript
692
+ // Compile-time path parameter validation
693
+ await client.get('/users/:id', {
694
+ pathParams: { id: '123' }, // ✓ Valid
695
+ })
696
+
697
+ await client.get('/users/:id', {
698
+ pathParams: { userId: '123' }, // ✗ TypeScript error: 'id' is required
699
+ })
700
+ ```
701
+
702
+ Optional path params are supported only for trailing final segments like `/users/:id?`.
703
+ Shapes like `/users/:id?/cards/:cardId` are rejected at compile time and runtime.
704
+
705
+ ### Request Options Type
706
+
707
+ ```typescript
708
+ interface HttpRequestOptions<
709
+ TBody = unknown,
710
+ TParams extends RequestParamsType = RequestParamsType,
711
+ > {
712
+ method?: HttpMethod
713
+ headers?: HttpHeaders
714
+ params?: TParams
715
+ pathParams?: Record<string, string | number | undefined>
716
+ body?: TBody
717
+ timeout?: number
718
+ signal?: AbortSignal
719
+ credentials?: RequestCredentials
720
+ cache?: RequestCache
721
+ mode?: RequestMode
722
+ redirect?: RequestRedirect
723
+ fetchOptions?: FetchOptions
724
+ onUploadStreaming?: (event: UploadStreamingEvent) => void
725
+ onDownloadStreaming?: (event: DownloadStreamingEvent) => void
726
+ responseType?: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData'
727
+ retry?: RetryOptions | boolean
728
+ }
729
+ ```
730
+
731
+ ### Response Types
732
+
733
+ ```typescript
734
+ interface ResponseType<T = unknown> {
735
+ data: T
736
+ status: number
737
+ statusText: string
738
+ headers: Record<string, string>
739
+ method: string
740
+ url: string
741
+ raw: Response
742
+ }
743
+
744
+ interface SchemaableResponse<T> extends Promise<ResponseType<T>> {
745
+ schema<S extends Schema>(schema: S): ExtractableResponse<InferSchemaOutput<S>>
746
+ data(): Promise<T>
747
+ void(): Promise<void>
748
+ }
749
+
750
+ interface ExtractableResponse<T> extends Promise<ResponseType<T>> {
751
+ data(): Promise<T>
752
+ void(): Promise<void>
753
+ }
754
+ ```