@kubb/plugin-axios 5.4.1 → 5.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-axios",
3
- "version": "5.4.1",
3
+ "version": "5.4.2",
4
4
  "description": "Generate a slim, type-safe HTTP client with Kubb based on axios.",
5
5
  "keywords": [
6
6
  "api-client",
@@ -66,18 +66,18 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@kubb/plugin-ts": "^5.0.2",
69
- "@kubb/plugin-zod": "^5.1.5"
69
+ "@kubb/plugin-zod": "^5.2.1"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@internals/client": "0.0.0",
73
73
  "@internals/shared": "0.0.0",
74
74
  "axios": "^1.20.0",
75
- "kubb": "^5.2.0",
75
+ "kubb": "^5.3.13",
76
76
  "typescript": "^6.0.3"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "axios": "^1.20.0",
80
- "kubb": "^5.2.0"
80
+ "kubb": "^5.3.13"
81
81
  },
82
82
  "peerDependenciesMeta": {
83
83
  "axios": {
package/src/plugin.ts CHANGED
@@ -1,7 +1,8 @@
1
+ import { readFileSync } from 'node:fs'
1
2
  import path from 'node:path'
2
3
  import { createSdkGenerator, defaultMacros, resolverClient } from '@internals/client'
3
4
  import { createGroupConfig } from '@internals/shared'
4
- import { definePlugin, Resolver } from 'kubb/kit'
5
+ import { ast, definePlugin, Resolver } from 'kubb/kit'
5
6
  import { pluginTsName } from '@kubb/plugin-ts'
6
7
  import { pluginZodName } from '@kubb/plugin-zod'
7
8
  import { clientGenerator } from './generators/clientGenerator.tsx'
@@ -90,10 +91,45 @@ export const pluginAxios = definePlugin<PluginAxios>((options) => {
90
91
  copy: axiosSerializersTemplatePath,
91
92
  })
92
93
 
94
+ const clientPath = path.resolve(root, '.kubb/client.ts')
95
+ const clientSource = readFileSync(axiosClientTemplatePath, 'utf8')
96
+ const clientBody = clientSource.slice(clientSource.indexOf('\n\n') + 2)
97
+ const runtimeRoot = path.dirname(clientPath)
98
+
93
99
  ctx.injectFile({
94
100
  baseName: 'client.ts',
95
- path: path.resolve(root, '.kubb/client.ts'),
96
- copy: axiosClientTemplatePath,
101
+ path: clientPath,
102
+ imports: [
103
+ ast.factory.createImport({ name: 'axios', path: 'axios' }),
104
+ ast.factory.createImport({
105
+ name: ['AxiosError', 'AxiosInstance', 'AxiosRequestConfig', 'AxiosResponse', 'InternalAxiosRequestConfig'],
106
+ path: 'axios',
107
+ isTypeOnly: true,
108
+ }),
109
+ ast.factory.createImport({
110
+ name: ['applyHeaderStyles', 'defaultBodySerializer', 'defaultPathSerializer', 'defaultQuerySerializer', 'isDefaultJsonBody', 'serializeCookies'],
111
+ path: path.resolve(root, '.kubb/serializers.ts'),
112
+ root: runtimeRoot,
113
+ }),
114
+ ast.factory.createImport({
115
+ name: ['HeadersInit', 'PathParamStyle', 'PathSerializer', 'Serializers', 'Styles'],
116
+ path: path.resolve(root, '.kubb/serializers.ts'),
117
+ root: runtimeRoot,
118
+ isTypeOnly: true,
119
+ }),
120
+ ast.factory.createImport({
121
+ name: ['ParseError', 'validateStandardSchema'],
122
+ path: path.resolve(root, '.kubb/standardSchema.ts'),
123
+ root: runtimeRoot,
124
+ }),
125
+ ast.factory.createImport({
126
+ name: ['StandardSchemaValidator'],
127
+ path: path.resolve(root, '.kubb/standardSchema.ts'),
128
+ root: runtimeRoot,
129
+ isTypeOnly: true,
130
+ }),
131
+ ],
132
+ sources: [ast.factory.createSource({ nodes: [ast.factory.createText(clientBody)] })],
97
133
  footer: baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : undefined,
98
134
  })
99
135
 
@@ -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.
@@ -289,24 +289,27 @@ export type CallResult<TRequest = AxiosRequestConfig, TResponse = AxiosResponse>
289
289
  response: TResponse
290
290
  }
291
291
 
292
- export type InterceptorFn<T> = (value: T) => T | Promise<T>
292
+ export type InterceptorFn<T, TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = (
293
+ value: T,
294
+ requestConfig?: RequestConfig<unknown, TRequest, TResponse>,
295
+ ) => T | Promise<T>
293
296
 
294
297
  /**
295
298
  * A single interceptor channel with a transport-agnostic `use` / `eject` / `update` API, backed by axios's native interceptor managers.
296
299
  */
297
- export type InterceptorChannel<T> = {
298
- use: (fn: InterceptorFn<T>) => number
300
+ export type InterceptorChannel<T, TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = {
301
+ use: (fn: InterceptorFn<T, TRequest, TResponse>) => number
299
302
  eject: (id: number) => void
300
- update: (id: number, fn: InterceptorFn<T>) => void
303
+ update: (id: number, fn: InterceptorFn<T, TRequest, TResponse>) => void
301
304
  }
302
305
 
303
306
  /**
304
307
  * The three interceptor channels every client instance exposes, wrapping axios's native managers with `error` mapped onto the response rejection handler.
305
308
  */
306
- export type Interceptors = {
307
- request: InterceptorChannel<InternalAxiosRequestConfig>
308
- response: InterceptorChannel<AxiosResponse>
309
- error: InterceptorChannel<AxiosError>
309
+ export type Interceptors<TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = {
310
+ request: InterceptorChannel<InternalAxiosRequestConfig, TRequest, TResponse>
311
+ response: InterceptorChannel<AxiosResponse, TRequest, TResponse>
312
+ error: InterceptorChannel<AxiosError, TRequest, TResponse>
310
313
  }
311
314
 
312
315
  /**
@@ -318,7 +321,7 @@ export type ClientInstance<TRequest = AxiosRequestConfig, TResponse = AxiosRespo
318
321
  getConfig: () => ClientConfig
319
322
  setConfig: (config: ClientConfig) => ClientConfig
320
323
  getUrl: <TBody = unknown>(config: RequestConfig<TBody, TRequest, TResponse>) => string
321
- interceptors: Interceptors
324
+ interceptors: Interceptors<TRequest, TResponse>
322
325
  createClient: (config?: ClientConfig) => ClientInstance<TRequest, TResponse>
323
326
  }
324
327
 
@@ -397,13 +400,27 @@ function serializeUrl({
397
400
  /**
398
401
  * 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
402
  */
400
- function createInterceptorChannel<T>(register: (fn: InterceptorFn<T>) => number, ejectNative: (id: number) => void): InterceptorChannel<T> {
403
+ function createInterceptorChannel<T, TRequest, TResponse>(
404
+ register: (fn: InterceptorFn<T, TRequest, TResponse>) => number,
405
+ ejectNative: (id: number) => void,
406
+ getRequestConfig: (value: T) => RequestConfig<unknown, TRequest, TResponse> | undefined,
407
+ getContextId: (value: T) => number | undefined,
408
+ setContextId: (value: T, id: number | undefined) => void,
409
+ ): InterceptorChannel<T, TRequest, TResponse> {
401
410
  const ids = new Map<number, number>()
402
411
  let counter = 0
412
+ const registerWithContext = (fn: InterceptorFn<T, TRequest, TResponse>) =>
413
+ register(async (value) => {
414
+ const contextId = getContextId(value)
415
+ const result = await fn(value, getRequestConfig(value))
416
+ if (contextId !== undefined) setContextId(result, contextId)
417
+ return result
418
+ })
419
+
403
420
  return {
404
421
  use(fn) {
405
422
  const id = ++counter
406
- ids.set(id, register(fn))
423
+ ids.set(id, registerWithContext(fn))
407
424
  return id
408
425
  },
409
426
  eject(id) {
@@ -415,7 +432,7 @@ function createInterceptorChannel<T>(register: (fn: InterceptorFn<T>) => number,
415
432
  update(id, fn) {
416
433
  const nativeId = ids.get(id)
417
434
  if (nativeId !== undefined) ejectNative(nativeId)
418
- ids.set(id, register(fn))
435
+ ids.set(id, registerWithContext(fn))
419
436
  },
420
437
  }
421
438
  }
@@ -665,53 +682,89 @@ async function settleResponse<TRequest, TResponse>({
665
682
  export function createClientCore<TRequest = AxiosRequestConfig, TResponse = AxiosResponse>(options: ClientConfig = {}): ClientInstance<TRequest, TResponse> {
666
683
  let config: ClientConfig = { ...options }
667
684
  const instance = config.transport ?? axios.create()
685
+ const requestContexts = new Map<number, RequestConfig<unknown, TRequest, TResponse>>()
686
+ let requestContextId = 0
687
+ const getContextId = (value: AxiosRequestConfig | AxiosResponse | AxiosError) => {
688
+ if (!value || typeof value !== 'object') return undefined
689
+ const axiosConfig = 'config' in value ? value.config : value
690
+ return (axiosConfig as (AxiosRequestConfig & { __kubbRequestContext?: number }) | undefined)?.__kubbRequestContext
691
+ }
692
+ const getRequestConfig = (value: AxiosRequestConfig | AxiosResponse | AxiosError) => {
693
+ const id = getContextId(value)
694
+ return id === undefined ? undefined : requestContexts.get(id)
695
+ }
696
+ const setContextId = (value: AxiosRequestConfig | AxiosResponse | AxiosError, id: number | undefined) => {
697
+ if (!value || typeof value !== 'object') return
698
+ const axiosConfig = 'config' in value ? value.config : value
699
+ if (!axiosConfig) return
700
+ const contextualConfig = axiosConfig as AxiosRequestConfig & { __kubbRequestContext?: number }
701
+ if (id === undefined) delete contextualConfig.__kubbRequestContext
702
+ else contextualConfig.__kubbRequestContext = id
703
+ }
668
704
 
669
705
  const requestManager = instance.interceptors.request
670
706
  const responseManager = instance.interceptors.response
671
- const interceptors: Interceptors = {
672
- request: createInterceptorChannel<InternalAxiosRequestConfig>(
707
+ const interceptors: Interceptors<TRequest, TResponse> = {
708
+ request: createInterceptorChannel<InternalAxiosRequestConfig, TRequest, TResponse>(
673
709
  (fn) => requestManager.use(fn),
674
710
  (id) => requestManager.eject(id),
711
+ getRequestConfig,
712
+ getContextId,
713
+ setContextId,
675
714
  ),
676
- response: createInterceptorChannel<AxiosResponse>(
715
+ response: createInterceptorChannel<AxiosResponse, TRequest, TResponse>(
677
716
  (fn) => responseManager.use(fn),
678
717
  (id) => responseManager.eject(id),
718
+ getRequestConfig,
719
+ getContextId,
720
+ setContextId,
679
721
  ),
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
- }),
722
+ error: createInterceptorChannel<AxiosError, TRequest, TResponse>(
723
+ (fn) => responseManager.use(undefined, (error: unknown) => Promise.resolve(fn(error as AxiosError)).then(() => Promise.reject(error))),
686
724
  (id) => responseManager.eject(id),
725
+ getRequestConfig,
726
+ getContextId,
727
+ setContextId,
687
728
  ),
688
729
  }
689
730
 
690
731
  const client = (async <TBody = unknown>(requestConfig: RequestConfig<TBody, TRequest, TResponse>): Promise<CallResult<TRequest, TResponse>> => {
691
732
  const activeInstance = requestConfig.transport ?? config.transport ?? instance
692
733
  const { axiosConfig, codecs, throwOnError } = await resolveRequest({ config, requestConfig })
734
+ const contextId = ++requestContextId
735
+ requestContexts.set(contextId, requestConfig)
736
+ ;(axiosConfig as AxiosRequestConfig & { __kubbRequestContext: number }).__kubbRequestContext = contextId
693
737
 
694
738
  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,
739
+ try {
740
+ const response = await activeInstance.request<unknown, AxiosResponse>(axiosConfig)
741
+ const result = await settleResponse<TRequest, TResponse>({
742
+ response,
743
+ codecs,
744
+ validator: requestConfig.validator,
745
+ onValidationError: requestConfig.onValidationError ?? config.onValidationError,
712
746
  })
747
+ setContextId(response, undefined)
748
+ return result
749
+ } catch (error) {
750
+ const axiosError = error as AxiosError
751
+ setContextId(axiosError, undefined)
752
+ if (axiosError.response) setContextId(axiosError.response, undefined)
753
+ if (throwOnError && axiosError.response) {
754
+ throw new ResponseError({
755
+ data: axiosError.response.data,
756
+ status: axiosError.response.status,
757
+ statusText: axiosError.response.statusText,
758
+ contentType: getResponseContentType(axiosError.response.headers as Record<string, unknown>),
759
+ request: axiosError.config as TRequest,
760
+ response: axiosError.response as TResponse,
761
+ })
762
+ }
763
+ throw error
713
764
  }
714
- throw error
765
+ } finally {
766
+ setContextId(axiosConfig, undefined)
767
+ requestContexts.delete(contextId)
715
768
  }
716
769
  }) as ClientInstance<TRequest, TResponse>
717
770