@kubb/plugin-fetch 5.4.0-canary.20260910T114055 → 5.4.1
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 +5 -5
- package/templates/fetch.ts +74 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-fetch",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.1",
|
|
4
4
|
"description": "Generate a slim, type-safe HTTP client with Kubb based on the native Fetch api.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-client",
|
|
@@ -65,12 +65,12 @@
|
|
|
65
65
|
"registry": "https://registry.npmjs.org/"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@kubb/plugin-ts": "^5.
|
|
69
|
-
"@kubb/plugin-zod": "^5.
|
|
68
|
+
"@kubb/plugin-ts": "^5.0.2",
|
|
69
|
+
"@kubb/plugin-zod": "^5.1.5"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@internals/client": "0.
|
|
73
|
-
"@internals/shared": "0.
|
|
72
|
+
"@internals/client": "0.0.0",
|
|
73
|
+
"@internals/shared": "0.0.0",
|
|
74
74
|
"kubb": "^5.2.0",
|
|
75
75
|
"typescript": "^6.0.3"
|
|
76
76
|
},
|
package/templates/fetch.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { applyHeaderStyles, defaultBodySerializer, defaultPathSerializer, defaultQuerySerializer, isDefaultJsonBody, serializeCookies } from './serializers'
|
|
2
2
|
import type { HeadersInit, PathParamStyle, PathSerializer, RequestBody, Serializers, Styles } from './serializers'
|
|
3
|
-
import { type StandardSchemaValidator, validateStandardSchema } from './standardSchema'
|
|
3
|
+
import { ParseError, type StandardSchemaValidator, validateStandardSchema } from './standardSchema'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* HTTP status codes treated as a success, everything else is an error.
|
|
@@ -170,6 +170,25 @@ export type ContentType = string | { request?: string; response?: string }
|
|
|
170
170
|
*/
|
|
171
171
|
export type Validator<T = unknown> = StandardSchemaValidator<T>
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* The failing body and the call it came from, handed to `onValidationError` alongside the `ParseError`.
|
|
175
|
+
* `direction` says which slot rejected it: the request body, the success body, or the error body.
|
|
176
|
+
*/
|
|
177
|
+
export type ValidationErrorContext = {
|
|
178
|
+
value: unknown
|
|
179
|
+
direction: 'request' | 'response' | 'error'
|
|
180
|
+
method?: string
|
|
181
|
+
url?: string
|
|
182
|
+
status?: number
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Decides what a failed validation does. Returning nothing rethrows the `ParseError`; returning
|
|
187
|
+
* `{ value }` resolves the call with that value instead, so a drifted body can be reported and still
|
|
188
|
+
* delivered. The box keeps an explicit `{ value: undefined }` substitution distinct from declining.
|
|
189
|
+
*/
|
|
190
|
+
export type ValidationErrorHandler = (error: ParseError, context: ValidationErrorContext) => { value: unknown } | void | Promise<{ value: unknown } | void>
|
|
191
|
+
|
|
173
192
|
/**
|
|
174
193
|
* A resolved security scheme carried on each generated call's `security` array and passed to the `auth` resolver.
|
|
175
194
|
*/
|
|
@@ -202,12 +221,12 @@ export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Respo
|
|
|
202
221
|
baseURL?: string
|
|
203
222
|
url?: string
|
|
204
223
|
method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
|
|
205
|
-
path?:
|
|
224
|
+
path?: unknown
|
|
206
225
|
query?: unknown
|
|
207
226
|
params?: unknown
|
|
208
|
-
cookies?:
|
|
227
|
+
cookies?: unknown
|
|
209
228
|
body?: TBody
|
|
210
|
-
headers?:
|
|
229
|
+
headers?: unknown
|
|
211
230
|
styles?: Styles
|
|
212
231
|
signal?: AbortSignal
|
|
213
232
|
credentials?: RequestCredentials
|
|
@@ -220,6 +239,7 @@ export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Respo
|
|
|
220
239
|
serializer?: Serializers
|
|
221
240
|
codecs?: Record<string, Codec>
|
|
222
241
|
validator?: { request?: Validator; response?: Validator; error?: Validator }
|
|
242
|
+
onValidationError?: ValidationErrorHandler
|
|
223
243
|
security?: Array<Auth>
|
|
224
244
|
auth?: AuthResolver
|
|
225
245
|
}
|
|
@@ -249,6 +269,7 @@ export type ClientConfig<TRequest = Request, TResponse = Response> = {
|
|
|
249
269
|
transport?: Transport<TRequest, TResponse>
|
|
250
270
|
serializer?: Serializers
|
|
251
271
|
codecs?: Record<string, Codec>
|
|
272
|
+
onValidationError?: ValidationErrorHandler
|
|
252
273
|
auth?: AuthResolver
|
|
253
274
|
}
|
|
254
275
|
|
|
@@ -463,9 +484,26 @@ export async function resolveAuth(params: {
|
|
|
463
484
|
}
|
|
464
485
|
}
|
|
465
486
|
|
|
466
|
-
async function runValidator<T>(
|
|
487
|
+
async function runValidator<T>({
|
|
488
|
+
validator,
|
|
489
|
+
value,
|
|
490
|
+
context,
|
|
491
|
+
onValidationError,
|
|
492
|
+
}: {
|
|
493
|
+
validator: Validator<T> | undefined
|
|
494
|
+
value: T
|
|
495
|
+
context: Omit<ValidationErrorContext, 'value'>
|
|
496
|
+
onValidationError: ValidationErrorHandler | undefined
|
|
497
|
+
}): Promise<T> {
|
|
467
498
|
if (!validator) return value
|
|
468
|
-
|
|
499
|
+
try {
|
|
500
|
+
return await validateStandardSchema(validator, value)
|
|
501
|
+
} catch (error) {
|
|
502
|
+
if (!onValidationError || !(error instanceof ParseError)) throw error
|
|
503
|
+
const handled = await onValidationError(error, { ...context, value })
|
|
504
|
+
if (!handled) throw error
|
|
505
|
+
return handled.value as T
|
|
506
|
+
}
|
|
469
507
|
}
|
|
470
508
|
|
|
471
509
|
/**
|
|
@@ -520,7 +558,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
|
|
|
520
558
|
const { querySerializer, bodySerializer, pathSerializer } = resolveSerializers({ config, requestConfig })
|
|
521
559
|
const codecs = { ...config.codecs, ...requestConfig.codecs }
|
|
522
560
|
|
|
523
|
-
const headers = mergeHeaders(config.headers, applyHeaderStyles(requestConfig.headers, requestConfig.styles?.header))
|
|
561
|
+
const headers = mergeHeaders(config.headers, applyHeaderStyles(requestConfig.headers as HeadersInit | undefined, requestConfig.styles?.header))
|
|
524
562
|
const { request: requestContentTypeOption, response: responseContentType } = resolveContentType(requestConfig.contentType)
|
|
525
563
|
const requestContentType = requestContentTypeOption ?? getHeader(headers, 'content-type')
|
|
526
564
|
if (responseContentType && !hasHeader(headers, 'accept')) {
|
|
@@ -537,11 +575,16 @@ async function resolveRequest<TBody, TRequest, TResponse>({
|
|
|
537
575
|
})
|
|
538
576
|
|
|
539
577
|
if (requestConfig.cookies) {
|
|
540
|
-
const cookie = serializeCookies(requestConfig.cookies, requestConfig.styles?.cookie)
|
|
578
|
+
const cookie = serializeCookies(requestConfig.cookies as Record<string, unknown>, requestConfig.styles?.cookie)
|
|
541
579
|
if (cookie) headers['Cookie'] = [headers['Cookie'], cookie].filter(Boolean).join('; ')
|
|
542
580
|
}
|
|
543
581
|
|
|
544
|
-
const validatedBody = await runValidator(
|
|
582
|
+
const validatedBody = await runValidator({
|
|
583
|
+
validator: requestConfig.validator?.request,
|
|
584
|
+
value: requestConfig.body,
|
|
585
|
+
context: { direction: 'request', method: requestConfig.method, url: requestConfig.url },
|
|
586
|
+
onValidationError: requestConfig.onValidationError ?? config.onValidationError,
|
|
587
|
+
})
|
|
545
588
|
const requestContentTypeBase = baseContentType(requestContentType)
|
|
546
589
|
const contentCodec = requestContentTypeBase ? codecs[requestContentTypeBase] : undefined
|
|
547
590
|
const usesDefaultBodySerializer = !contentCodec?.serialize && bodySerializer === defaultBodySerializer
|
|
@@ -561,7 +604,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
|
|
|
561
604
|
|
|
562
605
|
const url = serializeUrl({
|
|
563
606
|
parts: [requestConfig.baseURL ?? config.baseURL, requestConfig.url],
|
|
564
|
-
pathParams: requestConfig.path ?? {},
|
|
607
|
+
pathParams: (requestConfig.path ?? {}) as Record<string, unknown>,
|
|
565
608
|
search: querySerializer(query, requestConfig.styles?.query),
|
|
566
609
|
pathSerializer,
|
|
567
610
|
pathStyles: requestConfig.styles?.path,
|
|
@@ -591,15 +634,19 @@ async function resolveRequest<TBody, TRequest, TResponse>({
|
|
|
591
634
|
*/
|
|
592
635
|
async function settleResult<TRequest, TResponse>({
|
|
593
636
|
result,
|
|
637
|
+
request,
|
|
594
638
|
codecs,
|
|
595
639
|
throwOnError,
|
|
596
640
|
validator,
|
|
641
|
+
onValidationError,
|
|
597
642
|
errorInterceptors,
|
|
598
643
|
}: {
|
|
599
644
|
result: TransportResult<unknown, TRequest, TResponse>
|
|
645
|
+
request: ResolvedRequest
|
|
600
646
|
codecs: Record<string, Codec>
|
|
601
647
|
throwOnError: boolean
|
|
602
648
|
validator: { response?: Validator; error?: Validator } | undefined
|
|
649
|
+
onValidationError: ValidationErrorHandler | undefined
|
|
603
650
|
errorInterceptors: InterceptorStack<ResponseError<unknown, TRequest, TResponse>>
|
|
604
651
|
}): Promise<CallResult<TRequest, TResponse>> {
|
|
605
652
|
const isSuccess = result.status >= 200 && result.status < 300
|
|
@@ -610,12 +657,24 @@ async function settleResult<TRequest, TResponse>({
|
|
|
610
657
|
if (codec?.deserialize) decoded = await codec.deserialize(result.data, contentType)
|
|
611
658
|
}
|
|
612
659
|
|
|
660
|
+
const validationContext = { method: request.method, url: request.url, status: result.status }
|
|
661
|
+
|
|
613
662
|
if (isSuccess) {
|
|
614
|
-
const data = await runValidator(
|
|
663
|
+
const data = await runValidator({
|
|
664
|
+
validator: validator?.response,
|
|
665
|
+
value: decoded,
|
|
666
|
+
context: { direction: 'response', ...validationContext },
|
|
667
|
+
onValidationError,
|
|
668
|
+
})
|
|
615
669
|
return { status: result.status, data, error: undefined, contentType, request: result.request, response: result.response }
|
|
616
670
|
}
|
|
617
671
|
|
|
618
|
-
const error = await runValidator(
|
|
672
|
+
const error = await runValidator({
|
|
673
|
+
validator: validator?.error,
|
|
674
|
+
value: decoded,
|
|
675
|
+
context: { direction: 'error', ...validationContext },
|
|
676
|
+
onValidationError,
|
|
677
|
+
})
|
|
619
678
|
if (throwOnError) {
|
|
620
679
|
const responseError = new ResponseError({
|
|
621
680
|
data: error,
|
|
@@ -655,9 +714,11 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
655
714
|
|
|
656
715
|
return settleResult({
|
|
657
716
|
result,
|
|
717
|
+
request: resolvedRequest,
|
|
658
718
|
codecs,
|
|
659
719
|
throwOnError: requestConfig.throwOnError ?? config.throwOnError ?? true,
|
|
660
720
|
validator: requestConfig.validator,
|
|
721
|
+
onValidationError: requestConfig.onValidationError ?? config.onValidationError,
|
|
661
722
|
errorInterceptors: interceptors.error,
|
|
662
723
|
})
|
|
663
724
|
}) as ClientInstance<TRequest, TResponse>
|
|
@@ -672,7 +733,7 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
672
733
|
const query: Record<string, unknown> = { ...((requestConfig.query ?? requestConfig.params) as Record<string, unknown> | undefined) }
|
|
673
734
|
return serializeUrl({
|
|
674
735
|
parts: [requestConfig.baseURL ?? config.baseURL, requestConfig.url],
|
|
675
|
-
pathParams: requestConfig.path ?? {},
|
|
736
|
+
pathParams: (requestConfig.path ?? {}) as Record<string, unknown>,
|
|
676
737
|
search: querySerializer(query, requestConfig.styles?.query),
|
|
677
738
|
pathSerializer,
|
|
678
739
|
pathStyles: requestConfig.styles?.path,
|