@kubb/plugin-axios 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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/templates/axios.ts +83 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-axios",
3
- "version": "5.4.0-canary.20260910T114055",
3
+ "version": "5.4.1",
4
4
  "description": "Generate a slim, type-safe HTTP client with Kubb based on axios.",
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.1.0-canary.20260910T114055",
69
- "@kubb/plugin-zod": "^5.2.0-canary.20260910T114055"
68
+ "@kubb/plugin-ts": "^5.0.2",
69
+ "@kubb/plugin-zod": "^5.1.5"
70
70
  },
71
71
  "devDependencies": {
72
- "@internals/client": "0.1.0-canary.20260910T114055",
73
- "@internals/shared": "0.1.0-canary.20260910T114055",
72
+ "@internals/client": "0.0.0",
73
+ "@internals/shared": "0.0.0",
74
74
  "axios": "^1.20.0",
75
75
  "kubb": "^5.2.0",
76
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
  */
@@ -206,12 +225,12 @@ export type RequestConfig<TBody = unknown, TRequest = AxiosRequestConfig, TRespo
206
225
  baseURL?: string
207
226
  url?: string
208
227
  method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
209
- path?: Record<string, unknown>
228
+ path?: unknown
210
229
  query?: unknown
211
230
  params?: unknown
212
- cookies?: Record<string, unknown>
231
+ cookies?: unknown
213
232
  body?: TBody
214
- headers?: HeadersInit
233
+ headers?: unknown
215
234
  styles?: Styles
216
235
  signal?: AbortSignal
217
236
  options?: AxiosOptions
@@ -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
  /**
@@ -488,7 +526,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
488
526
  const { querySerializer, bodySerializer, pathSerializer } = resolveSerializers({ config, requestConfig })
489
527
  const codecs = { ...config.codecs, ...requestConfig.codecs }
490
528
 
491
- const headers = mergeHeaders(config.headers, applyHeaderStyles(requestConfig.headers, requestConfig.styles?.header))
529
+ const headers = mergeHeaders(config.headers, applyHeaderStyles(requestConfig.headers as HeadersInit | undefined, requestConfig.styles?.header))
492
530
  const { request: requestContentTypeOption, response: responseContentType } = resolveContentType(requestConfig.contentType)
493
531
  const requestContentType = requestContentTypeOption ?? getHeader(headers, 'content-type')
494
532
  if (responseContentType && !hasHeader(headers, 'accept')) {
@@ -505,11 +543,16 @@ async function resolveRequest<TBody, TRequest, TResponse>({
505
543
  })
506
544
 
507
545
  if (requestConfig.cookies) {
508
- const cookie = serializeCookies(requestConfig.cookies, requestConfig.styles?.cookie)
546
+ const cookie = serializeCookies(requestConfig.cookies as Record<string, unknown>, requestConfig.styles?.cookie)
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
@@ -527,7 +570,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
527
570
  headers['Content-Type'] = 'application/json'
528
571
  }
529
572
 
530
- const pathParams = requestConfig.path ?? {}
573
+ const pathParams = (requestConfig.path ?? {}) as Record<string, unknown>
531
574
  const url = (requestConfig.url ?? '').replace(/\{([^{}]+)\}/g, (_, key: string) =>
532
575
  pathSerializer({ name: key, value: pathParams[key], options: requestConfig.styles?.path?.[key] }),
533
576
  )
@@ -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) {
@@ -656,7 +725,7 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
656
725
  const query: Record<string, unknown> = { ...((requestConfig.query ?? requestConfig.params) as Record<string, unknown> | undefined) }
657
726
  return serializeUrl({
658
727
  parts: [requestConfig.baseURL ?? config.baseURL, requestConfig.url],
659
- pathParams: requestConfig.path ?? {},
728
+ pathParams: (requestConfig.path ?? {}) as Record<string, unknown>,
660
729
  search: querySerializer(query, requestConfig.styles?.query),
661
730
  pathSerializer,
662
731
  pathStyles: requestConfig.styles?.path,