@kubb/plugin-axios 5.4.0-canary.20260910T110004 → 5.4.0

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.
Files changed (2) hide show
  1. package/package.json +9 -5
  2. package/templates/axios.ts +76 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-axios",
3
- "version": "5.4.0-canary.20260910T110004",
3
+ "version": "5.4.0",
4
4
  "description": "Generate a slim, type-safe HTTP client with Kubb based on axios.",
5
5
  "keywords": [
6
6
  "api-client",
@@ -29,6 +29,10 @@
29
29
  {
30
30
  "type": "opencollective",
31
31
  "url": "https://opencollective.com/kubb"
32
+ },
33
+ {
34
+ "type": "custom",
35
+ "url": "https://kubb.dev/sponsors"
32
36
  }
33
37
  ],
34
38
  "files": [
@@ -61,12 +65,12 @@
61
65
  "registry": "https://registry.npmjs.org/"
62
66
  },
63
67
  "dependencies": {
64
- "@kubb/plugin-ts": "^5.1.0-canary.20260910T110004",
65
- "@kubb/plugin-zod": "^5.2.0-canary.20260910T110004"
68
+ "@kubb/plugin-ts": "^5.0.2",
69
+ "@kubb/plugin-zod": "^5.1.5"
66
70
  },
67
71
  "devDependencies": {
68
- "@internals/client": "0.1.0-canary.20260910T110004",
69
- "@internals/shared": "0.1.0-canary.20260910T110004",
72
+ "@internals/client": "0.0.0",
73
+ "@internals/shared": "0.0.0",
70
74
  "axios": "^1.20.0",
71
75
  "kubb": "^5.2.0",
72
76
  "typescript": "^6.0.3"
@@ -2,7 +2,7 @@ import axios from 'axios'
2
2
  import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
3
3
  import { applyHeaderStyles, defaultBodySerializer, defaultPathSerializer, defaultQuerySerializer, isDefaultJsonBody, serializeCookies } from './serializers'
4
4
  import type { HeadersInit, PathParamStyle, PathSerializer, Serializers, Styles } from './serializers'
5
- import { type StandardSchemaValidator, validateStandardSchema } from './standardSchema'
5
+ import { ParseError, type StandardSchemaValidator, validateStandardSchema } from './standardSchema'
6
6
 
7
7
  /**
8
8
  * HTTP status codes treated as a success, everything else is an error.
@@ -174,6 +174,25 @@ export type ContentType = string | { request?: string; response?: string }
174
174
  */
175
175
  export type Validator<T = unknown> = StandardSchemaValidator<T>
176
176
 
177
+ /**
178
+ * The failing body and the call it came from, handed to `onValidationError` alongside the `ParseError`.
179
+ * `direction` says which slot rejected it: the request body, the success body, or the error body.
180
+ */
181
+ export type ValidationErrorContext = {
182
+ value: unknown
183
+ direction: 'request' | 'response' | 'error'
184
+ method?: string
185
+ url?: string
186
+ status?: number
187
+ }
188
+
189
+ /**
190
+ * Decides what a failed validation does. Returning nothing rethrows the `ParseError`; returning
191
+ * `{ value }` resolves the call with that value instead, so a drifted body can be reported and still
192
+ * delivered. The box keeps an explicit `{ value: undefined }` substitution distinct from declining.
193
+ */
194
+ export type ValidationErrorHandler = (error: ParseError, context: ValidationErrorContext) => { value: unknown } | void | Promise<{ value: unknown } | void>
195
+
177
196
  /**
178
197
  * A resolved security scheme carried on each generated call's `security` array and passed to the `auth` resolver.
179
198
  */
@@ -224,6 +243,7 @@ export type RequestConfig<TBody = unknown, TRequest = AxiosRequestConfig, TRespo
224
243
  serializer?: Serializers
225
244
  codecs?: Record<string, Codec>
226
245
  validator?: { request?: Validator; response?: Validator; error?: Validator }
246
+ onValidationError?: ValidationErrorHandler
227
247
  security?: Array<Auth>
228
248
  auth?: AuthResolver
229
249
  }
@@ -253,6 +273,7 @@ export type ClientConfig = {
253
273
  transport?: AxiosInstance
254
274
  serializer?: Serializers
255
275
  codecs?: Record<string, Codec>
276
+ onValidationError?: ValidationErrorHandler
256
277
  auth?: AuthResolver
257
278
  }
258
279
 
@@ -431,9 +452,26 @@ export async function resolveAuth(params: {
431
452
  }
432
453
  }
433
454
 
434
- async function runValidator<T>(validator: Validator<T> | undefined, value: T): Promise<T> {
455
+ async function runValidator<T>({
456
+ validator,
457
+ value,
458
+ context,
459
+ onValidationError,
460
+ }: {
461
+ validator: Validator<T> | undefined
462
+ value: T
463
+ context: Omit<ValidationErrorContext, 'value'>
464
+ onValidationError: ValidationErrorHandler | undefined
465
+ }): Promise<T> {
435
466
  if (!validator) return value
436
- return validateStandardSchema(validator, value)
467
+ try {
468
+ return await validateStandardSchema(validator, value)
469
+ } catch (error) {
470
+ if (!onValidationError || !(error instanceof ParseError)) throw error
471
+ const handled = await onValidationError(error, { ...context, value })
472
+ if (!handled) throw error
473
+ return handled.value as T
474
+ }
437
475
  }
438
476
 
439
477
  /**
@@ -509,7 +547,12 @@ async function resolveRequest<TBody, TRequest, TResponse>({
509
547
  if (cookie) headers['Cookie'] = [headers['Cookie'], cookie].filter(Boolean).join('; ')
510
548
  }
511
549
 
512
- const validatedBody = await runValidator(requestConfig.validator?.request, requestConfig.body)
550
+ const validatedBody = await runValidator({
551
+ validator: requestConfig.validator?.request,
552
+ value: requestConfig.body,
553
+ context: { direction: 'request', method: requestConfig.method, url: requestConfig.url },
554
+ onValidationError: requestConfig.onValidationError ?? config.onValidationError,
555
+ })
513
556
  const requestContentTypeBase = baseContentType(requestContentType)
514
557
  const contentCodec = requestContentTypeBase ? codecs[requestContentTypeBase] : undefined
515
558
  const usesDefaultBodySerializer = !contentCodec?.serialize && bodySerializer === defaultBodySerializer
@@ -571,10 +614,12 @@ async function settleResponse<TRequest, TResponse>({
571
614
  response,
572
615
  codecs,
573
616
  validator,
617
+ onValidationError,
574
618
  }: {
575
619
  response: AxiosResponse
576
620
  codecs: Record<string, Codec>
577
621
  validator: { response?: Validator; error?: Validator } | undefined
622
+ onValidationError: ValidationErrorHandler | undefined
578
623
  }): Promise<CallResult<TRequest, TResponse>> {
579
624
  const isSuccess = response.status >= 200 && response.status < 300
580
625
  const contentType = getResponseContentType(response.headers as Record<string, unknown>)
@@ -583,8 +628,27 @@ async function settleResponse<TRequest, TResponse>({
583
628
  const codec = codecs[contentType]
584
629
  if (codec?.deserialize) decoded = await codec.deserialize(response.data, contentType)
585
630
  }
586
- const data = isSuccess ? await runValidator(validator?.response, decoded) : undefined
587
- const error = isSuccess ? undefined : await runValidator(validator?.error, decoded)
631
+ const validationContext = {
632
+ method: response.config?.method?.toUpperCase(),
633
+ url: response.config?.url,
634
+ status: response.status,
635
+ }
636
+ const data = isSuccess
637
+ ? await runValidator({
638
+ validator: validator?.response,
639
+ value: decoded,
640
+ context: { direction: 'response', ...validationContext },
641
+ onValidationError,
642
+ })
643
+ : undefined
644
+ const error = isSuccess
645
+ ? undefined
646
+ : await runValidator({
647
+ validator: validator?.error,
648
+ value: decoded,
649
+ context: { direction: 'error', ...validationContext },
650
+ onValidationError,
651
+ })
588
652
  return {
589
653
  status: response.status,
590
654
  data,
@@ -629,7 +693,12 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
629
693
 
630
694
  try {
631
695
  const response = await activeInstance.request<unknown, AxiosResponse>(axiosConfig)
632
- return await settleResponse({ response, codecs, validator: requestConfig.validator })
696
+ return await settleResponse({
697
+ response,
698
+ codecs,
699
+ validator: requestConfig.validator,
700
+ onValidationError: requestConfig.onValidationError ?? config.onValidationError,
701
+ })
633
702
  } catch (error) {
634
703
  const axiosError = error as AxiosError
635
704
  if (throwOnError && axiosError.response) {