@kubb/plugin-axios 5.5.0-canary.20260924T110844 → 5.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-axios",
3
- "version": "5.5.0-canary.20260924T110844",
3
+ "version": "5.5.0",
4
4
  "description": "Generate a slim, type-safe HTTP client with Kubb based on axios.",
5
5
  "keywords": [
6
6
  "api-client",
@@ -65,19 +65,19 @@
65
65
  "registry": "https://registry.npmjs.org/"
66
66
  },
67
67
  "dependencies": {
68
- "@kubb/plugin-ts": "^5.1.0-canary.20260924T110844",
69
- "@kubb/plugin-zod": "^5.3.0-canary.20260924T110844"
68
+ "@kubb/plugin-ts": "^5.0.2",
69
+ "@kubb/plugin-zod": "^5.2.1"
70
70
  },
71
71
  "devDependencies": {
72
- "@internals/client": "0.1.0-canary.20260924T110844",
73
- "@internals/shared": "0.1.0-canary.20260924T110844",
72
+ "@internals/client": "0.0.0",
73
+ "@internals/shared": "0.0.0",
74
74
  "axios": "^1.20.0",
75
- "kubb": "^5.3.13",
75
+ "kubb": "^5.3.14",
76
76
  "typescript": "^6.0.3"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "axios": "^1.20.0",
80
- "kubb": "^5.3.13"
80
+ "kubb": "^5.3.14"
81
81
  },
82
82
  "peerDependenciesMeta": {
83
83
  "axios": {
package/src/plugin.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path'
2
- import { createSdkGenerator, defaultMacros, resolverClient } from '@internals/client'
2
+ import { createSdkGenerator, defaultMacros, resolverClient, runtimeTemplate } from '@internals/client'
3
3
  import { createGroupConfig } from '@internals/shared'
4
4
  import { definePlugin, Resolver } from 'kubb/kit'
5
5
  import { pluginTsName } from '@kubb/plugin-ts'
@@ -43,6 +43,7 @@ export const pluginAxios = definePlugin<PluginAxios>((options) => {
43
43
  include,
44
44
  override = [],
45
45
  baseURL,
46
+ throwOnErrorDefault = true,
46
47
  validator = false,
47
48
  returnType = 'full',
48
49
  group,
@@ -57,6 +58,7 @@ export const pluginAxios = definePlugin<PluginAxios>((options) => {
57
58
  override,
58
59
  group: createGroupConfig(group),
59
60
  baseURL,
61
+ throwOnErrorDefault,
60
62
  validator,
61
63
  returnType,
62
64
  sdk: sdk ? { mode: sdk.mode ?? 'tag', name: sdk.name } : undefined,
@@ -93,7 +95,8 @@ export const pluginAxios = definePlugin<PluginAxios>((options) => {
93
95
  ctx.injectFile({
94
96
  baseName: 'client.ts',
95
97
  path: path.resolve(root, '.kubb/client.ts'),
96
- copy: axiosClientTemplatePath,
98
+ // Keep sources empty so the barrel exports the whole runtime module.
99
+ banner: runtimeTemplate(axiosClientTemplatePath, ctx.config),
97
100
  footer: baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : undefined,
98
101
  })
99
102
 
@@ -1,8 +1,8 @@
1
1
  import axios from 'axios'
2
2
  import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
3
- import { applyHeaderStyles, defaultBodySerializer, defaultPathSerializer, defaultQuerySerializer, isDefaultJsonBody, serializeCookies } from './serializers'
4
- import type { HeadersInit, PathParamStyle, PathSerializer, Serializers, Styles } from './serializers'
5
- import { ParseError, type StandardSchemaValidator, validateStandardSchema } from './standardSchema'
3
+ import { applyHeaderStyles, defaultBodySerializer, defaultPathSerializer, defaultQuerySerializer, isDefaultJsonBody, serializeCookies } from './serializers.ts'
4
+ import type { HeadersInit, PathParamStyle, PathSerializer, Serializers, Styles } from './serializers.ts'
5
+ import { ParseError, type StandardSchemaValidator, validateStandardSchema } from './standardSchema.ts'
6
6
 
7
7
  /**
8
8
  * HTTP status codes treated as a success, everything else is an error.
@@ -111,10 +111,8 @@ export function withUnwrap<T extends { data: unknown; error: unknown }>(promise:
111
111
  }
112
112
 
113
113
  /**
114
- * The shape a generated operation returns when `returnType: 'data'` is set: the bare success body
115
- * once `throwOnError` (on by default) narrows away the error branch, falling back to the full
116
- * `RequestResult` when a call sets `throwOnError: false` and still needs `error` to discriminate a
117
- * failed response.
114
+ * The shape a generated operation returns with `returnType: 'data'`: the success body when
115
+ * `throwOnError` is true, or the full `RequestResult` when it is false.
118
116
  */
119
117
  export type UnwrappedResult<
120
118
  TResponses,
@@ -124,11 +122,8 @@ export type UnwrappedResult<
124
122
  > = ThrowOnError extends true ? RequestResult<TResponses, true, TRequest, TResponse>['data'] : RequestResult<TResponses, ThrowOnError, TRequest, TResponse>
125
123
 
126
124
  /**
127
- * Narrows a resolved call down to its success body once `throwOnError` (on by default) rules out
128
- * the error branch, the same default the runtime itself applies. Falls back to the full result for
129
- * a call that sets `throwOnError: false`, since that path still needs `error` to discriminate a
130
- * failed response. Backs `returnType: 'data'`, mirroring how `toEventStream` centralizes the
131
- * post-processing for `text/event-stream` operations.
125
+ * Returns the success body when `throwOnError` is true, or the full result when it is false.
126
+ * Backs generated operations with `returnType: 'data'`.
132
127
  */
133
128
  export function unwrapResult<T extends { data: unknown; error: unknown }>(promise: Promise<T>, throwOnError: boolean | undefined): Promise<T | T['data']> {
134
129
  return promise.then((result) => ((throwOnError ?? true) ? result.data : result))
@@ -289,24 +284,27 @@ export type CallResult<TRequest = AxiosRequestConfig, TResponse = AxiosResponse>
289
284
  response: TResponse
290
285
  }
291
286
 
292
- export type InterceptorFn<T> = (value: T) => T | Promise<T>
287
+ export type InterceptorFn<T, TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = (
288
+ value: T,
289
+ requestConfig?: RequestConfig<unknown, TRequest, TResponse>,
290
+ ) => T | Promise<T>
293
291
 
294
292
  /**
295
293
  * A single interceptor channel with a transport-agnostic `use` / `eject` / `update` API, backed by axios's native interceptor managers.
296
294
  */
297
- export type InterceptorChannel<T> = {
298
- use: (fn: InterceptorFn<T>) => number
295
+ export type InterceptorChannel<T, TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = {
296
+ use: (fn: InterceptorFn<T, TRequest, TResponse>) => number
299
297
  eject: (id: number) => void
300
- update: (id: number, fn: InterceptorFn<T>) => void
298
+ update: (id: number, fn: InterceptorFn<T, TRequest, TResponse>) => void
301
299
  }
302
300
 
303
301
  /**
304
302
  * The three interceptor channels every client instance exposes, wrapping axios's native managers with `error` mapped onto the response rejection handler.
305
303
  */
306
- export type Interceptors = {
307
- request: InterceptorChannel<InternalAxiosRequestConfig>
308
- response: InterceptorChannel<AxiosResponse>
309
- error: InterceptorChannel<AxiosError>
304
+ export type Interceptors<TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = {
305
+ request: InterceptorChannel<InternalAxiosRequestConfig, TRequest, TResponse>
306
+ response: InterceptorChannel<AxiosResponse, TRequest, TResponse>
307
+ error: InterceptorChannel<AxiosError, TRequest, TResponse>
310
308
  }
311
309
 
312
310
  /**
@@ -318,7 +316,7 @@ export type ClientInstance<TRequest = AxiosRequestConfig, TResponse = AxiosRespo
318
316
  getConfig: () => ClientConfig
319
317
  setConfig: (config: ClientConfig) => ClientConfig
320
318
  getUrl: <TBody = unknown>(config: RequestConfig<TBody, TRequest, TResponse>) => string
321
- interceptors: Interceptors
319
+ interceptors: Interceptors<TRequest, TResponse>
322
320
  createClient: (config?: ClientConfig) => ClientInstance<TRequest, TResponse>
323
321
  }
324
322
 
@@ -397,13 +395,27 @@ function serializeUrl({
397
395
  /**
398
396
  * Wraps an axios interceptor registration behind the shared `use` / `eject` / `update` API, mapping a stable external id onto axios's own so `update` can swap a handler in place.
399
397
  */
400
- function createInterceptorChannel<T>(register: (fn: InterceptorFn<T>) => number, ejectNative: (id: number) => void): InterceptorChannel<T> {
398
+ function createInterceptorChannel<T, TRequest, TResponse>(
399
+ register: (fn: InterceptorFn<T, TRequest, TResponse>) => number,
400
+ ejectNative: (id: number) => void,
401
+ getRequestConfig: (value: T) => RequestConfig<unknown, TRequest, TResponse> | undefined,
402
+ getContextId: (value: T) => number | undefined,
403
+ setContextId: (value: T, id: number | undefined) => void,
404
+ ): InterceptorChannel<T, TRequest, TResponse> {
401
405
  const ids = new Map<number, number>()
402
406
  let counter = 0
407
+ const registerWithContext = (fn: InterceptorFn<T, TRequest, TResponse>) =>
408
+ register(async (value) => {
409
+ const contextId = getContextId(value)
410
+ const result = await fn(value, getRequestConfig(value))
411
+ if (contextId !== undefined) setContextId(result, contextId)
412
+ return result
413
+ })
414
+
403
415
  return {
404
416
  use(fn) {
405
417
  const id = ++counter
406
- ids.set(id, register(fn))
418
+ ids.set(id, registerWithContext(fn))
407
419
  return id
408
420
  },
409
421
  eject(id) {
@@ -415,7 +427,7 @@ function createInterceptorChannel<T>(register: (fn: InterceptorFn<T>) => number,
415
427
  update(id, fn) {
416
428
  const nativeId = ids.get(id)
417
429
  if (nativeId !== undefined) ejectNative(nativeId)
418
- ids.set(id, register(fn))
430
+ ids.set(id, registerWithContext(fn))
419
431
  },
420
432
  }
421
433
  }
@@ -665,53 +677,89 @@ async function settleResponse<TRequest, TResponse>({
665
677
  export function createClientCore<TRequest = AxiosRequestConfig, TResponse = AxiosResponse>(options: ClientConfig = {}): ClientInstance<TRequest, TResponse> {
666
678
  let config: ClientConfig = { ...options }
667
679
  const instance = config.transport ?? axios.create()
680
+ const requestContexts = new Map<number, RequestConfig<unknown, TRequest, TResponse>>()
681
+ let requestContextId = 0
682
+ const getContextId = (value: AxiosRequestConfig | AxiosResponse | AxiosError) => {
683
+ if (!value || typeof value !== 'object') return undefined
684
+ const axiosConfig = 'config' in value ? value.config : value
685
+ return (axiosConfig as (AxiosRequestConfig & { __kubbRequestContext?: number }) | undefined)?.__kubbRequestContext
686
+ }
687
+ const getRequestConfig = (value: AxiosRequestConfig | AxiosResponse | AxiosError) => {
688
+ const id = getContextId(value)
689
+ return id === undefined ? undefined : requestContexts.get(id)
690
+ }
691
+ const setContextId = (value: AxiosRequestConfig | AxiosResponse | AxiosError, id: number | undefined) => {
692
+ if (!value || typeof value !== 'object') return
693
+ const axiosConfig = 'config' in value ? value.config : value
694
+ if (!axiosConfig) return
695
+ const contextualConfig = axiosConfig as AxiosRequestConfig & { __kubbRequestContext?: number }
696
+ if (id === undefined) delete contextualConfig.__kubbRequestContext
697
+ else contextualConfig.__kubbRequestContext = id
698
+ }
668
699
 
669
700
  const requestManager = instance.interceptors.request
670
701
  const responseManager = instance.interceptors.response
671
- const interceptors: Interceptors = {
672
- request: createInterceptorChannel<InternalAxiosRequestConfig>(
702
+ const interceptors: Interceptors<TRequest, TResponse> = {
703
+ request: createInterceptorChannel<InternalAxiosRequestConfig, TRequest, TResponse>(
673
704
  (fn) => requestManager.use(fn),
674
705
  (id) => requestManager.eject(id),
706
+ getRequestConfig,
707
+ getContextId,
708
+ setContextId,
675
709
  ),
676
- response: createInterceptorChannel<AxiosResponse>(
710
+ response: createInterceptorChannel<AxiosResponse, TRequest, TResponse>(
677
711
  (fn) => responseManager.use(fn),
678
712
  (id) => responseManager.eject(id),
713
+ getRequestConfig,
714
+ getContextId,
715
+ setContextId,
679
716
  ),
680
- error: createInterceptorChannel<AxiosError>(
681
- (fn) =>
682
- responseManager.use(undefined, async (error: unknown) => {
683
- await fn(error as AxiosError)
684
- return Promise.reject(error)
685
- }),
717
+ error: createInterceptorChannel<AxiosError, TRequest, TResponse>(
718
+ (fn) => responseManager.use(undefined, (error: unknown) => Promise.resolve(fn(error as AxiosError)).then(() => Promise.reject(error))),
686
719
  (id) => responseManager.eject(id),
720
+ getRequestConfig,
721
+ getContextId,
722
+ setContextId,
687
723
  ),
688
724
  }
689
725
 
690
726
  const client = (async <TBody = unknown>(requestConfig: RequestConfig<TBody, TRequest, TResponse>): Promise<CallResult<TRequest, TResponse>> => {
691
727
  const activeInstance = requestConfig.transport ?? config.transport ?? instance
692
728
  const { axiosConfig, codecs, throwOnError } = await resolveRequest({ config, requestConfig })
729
+ const contextId = ++requestContextId
730
+ requestContexts.set(contextId, requestConfig)
731
+ ;(axiosConfig as AxiosRequestConfig & { __kubbRequestContext: number }).__kubbRequestContext = contextId
693
732
 
694
733
  try {
695
- const response = await activeInstance.request<unknown, AxiosResponse>(axiosConfig)
696
- return await settleResponse({
697
- response,
698
- codecs,
699
- validator: requestConfig.validator,
700
- onValidationError: requestConfig.onValidationError ?? config.onValidationError,
701
- })
702
- } catch (error) {
703
- const axiosError = error as AxiosError
704
- if (throwOnError && axiosError.response) {
705
- throw new ResponseError({
706
- data: axiosError.response.data,
707
- status: axiosError.response.status,
708
- statusText: axiosError.response.statusText,
709
- contentType: getResponseContentType(axiosError.response.headers as Record<string, unknown>),
710
- request: axiosError.config as TRequest,
711
- response: axiosError.response as TResponse,
734
+ try {
735
+ const response = await activeInstance.request<unknown, AxiosResponse>(axiosConfig)
736
+ const result = await settleResponse<TRequest, TResponse>({
737
+ response,
738
+ codecs,
739
+ validator: requestConfig.validator,
740
+ onValidationError: requestConfig.onValidationError ?? config.onValidationError,
712
741
  })
742
+ setContextId(response, undefined)
743
+ return result
744
+ } catch (error) {
745
+ const axiosError = error as AxiosError
746
+ setContextId(axiosError, undefined)
747
+ if (axiosError.response) setContextId(axiosError.response, undefined)
748
+ if (throwOnError && axiosError.response) {
749
+ throw new ResponseError({
750
+ data: axiosError.response.data,
751
+ status: axiosError.response.status,
752
+ statusText: axiosError.response.statusText,
753
+ contentType: getResponseContentType(axiosError.response.headers as Record<string, unknown>),
754
+ request: axiosError.config as TRequest,
755
+ response: axiosError.response as TResponse,
756
+ })
757
+ }
758
+ throw error
713
759
  }
714
- throw error
760
+ } finally {
761
+ setContextId(axiosConfig, undefined)
762
+ requestContexts.delete(contextId)
715
763
  }
716
764
  }) as ClientInstance<TRequest, TResponse>
717
765