@kubb/plugin-fetch 5.4.0 → 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-fetch",
3
- "version": "5.4.0",
3
+ "version": "5.4.2",
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",
@@ -66,16 +66,16 @@
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
- "kubb": "^5.2.0",
74
+ "kubb": "^5.3.13",
75
75
  "typescript": "^6.0.3"
76
76
  },
77
77
  "peerDependencies": {
78
- "kubb": "^5.2.0"
78
+ "kubb": "^5.3.13"
79
79
  },
80
80
  "engines": {
81
81
  "node": ">=22"
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,39 @@ export const pluginFetch = definePlugin<PluginFetch>((options) => {
90
91
  copy: fetchSerializersTemplatePath,
91
92
  })
92
93
 
94
+ const clientPath = path.resolve(root, '.kubb/client.ts')
95
+ const clientSource = readFileSync(fetchClientTemplatePath, '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: fetchClientTemplatePath,
101
+ path: clientPath,
102
+ imports: [
103
+ ast.factory.createImport({
104
+ name: ['applyHeaderStyles', 'defaultBodySerializer', 'defaultPathSerializer', 'defaultQuerySerializer', 'isDefaultJsonBody', 'serializeCookies'],
105
+ path: path.resolve(root, '.kubb/serializers.ts'),
106
+ root: runtimeRoot,
107
+ }),
108
+ ast.factory.createImport({
109
+ name: ['HeadersInit', 'PathParamStyle', 'PathSerializer', 'RequestBody', 'Serializers', 'Styles'],
110
+ path: path.resolve(root, '.kubb/serializers.ts'),
111
+ root: runtimeRoot,
112
+ isTypeOnly: true,
113
+ }),
114
+ ast.factory.createImport({
115
+ name: ['ParseError', 'validateStandardSchema'],
116
+ path: path.resolve(root, '.kubb/standardSchema.ts'),
117
+ root: runtimeRoot,
118
+ }),
119
+ ast.factory.createImport({
120
+ name: ['StandardSchemaValidator'],
121
+ path: path.resolve(root, '.kubb/standardSchema.ts'),
122
+ root: runtimeRoot,
123
+ isTypeOnly: true,
124
+ }),
125
+ ],
126
+ sources: [ast.factory.createSource({ nodes: [ast.factory.createText(clientBody)] })],
97
127
  footer: baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : undefined,
98
128
  })
99
129
 
@@ -1,6 +1,6 @@
1
- import { applyHeaderStyles, defaultBodySerializer, defaultPathSerializer, defaultQuerySerializer, isDefaultJsonBody, serializeCookies } from './serializers'
2
- import type { HeadersInit, PathParamStyle, PathSerializer, RequestBody, Serializers, Styles } from './serializers'
3
- import { ParseError, type StandardSchemaValidator, validateStandardSchema } from './standardSchema'
1
+ import { applyHeaderStyles, defaultBodySerializer, defaultPathSerializer, defaultQuerySerializer, isDefaultJsonBody, serializeCookies } from './serializers.ts'
2
+ import type { HeadersInit, PathParamStyle, PathSerializer, RequestBody, Serializers, Styles } from './serializers.ts'
3
+ import { ParseError, type StandardSchemaValidator, validateStandardSchema } from './standardSchema.ts'
4
4
 
5
5
  /**
6
6
  * HTTP status codes treated as a success, everything else is an error.
@@ -221,12 +221,12 @@ export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Respo
221
221
  baseURL?: string
222
222
  url?: string
223
223
  method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
224
- path?: Record<string, unknown>
224
+ path?: unknown
225
225
  query?: unknown
226
226
  params?: unknown
227
- cookies?: Record<string, unknown>
227
+ cookies?: unknown
228
228
  body?: TBody
229
- headers?: HeadersInit
229
+ headers?: unknown
230
230
  styles?: Styles
231
231
  signal?: AbortSignal
232
232
  credentials?: RequestCredentials
@@ -317,25 +317,25 @@ export type CallResult<TRequest = Request, TResponse = Response> = {
317
317
  response: TResponse
318
318
  }
319
319
 
320
- export type InterceptorFn<T> = (value: T) => T | Promise<T>
320
+ export type InterceptorFn<T, TContext = never> = (value: T, context?: TContext) => T | Promise<T>
321
321
 
322
322
  /**
323
323
  * A single interceptor channel with a transport-agnostic `use` / `eject` / `update` API.
324
324
  */
325
- export type InterceptorStack<T> = {
326
- use: (fn: InterceptorFn<T>) => number
325
+ export type InterceptorStack<T, TContext = never> = {
326
+ use: (fn: InterceptorFn<T, TContext>) => number
327
327
  eject: (id: number) => void
328
- update: (id: number, fn: InterceptorFn<T>) => void
329
- run: (value: T) => Promise<T>
328
+ update: (id: number, fn: InterceptorFn<T, TContext>) => void
329
+ run: (value: T, context?: TContext) => Promise<T>
330
330
  }
331
331
 
332
332
  /**
333
333
  * The three interceptor channels every client instance exposes.
334
334
  */
335
335
  export type Interceptors<TRequest = Request, TResponse = Response> = {
336
- request: InterceptorStack<ResolvedRequest>
337
- response: InterceptorStack<TransportResult<unknown, TRequest, TResponse>>
338
- error: InterceptorStack<ResponseError<unknown, TRequest, TResponse>>
336
+ request: InterceptorStack<ResolvedRequest, RequestConfig<unknown, TRequest, TResponse>>
337
+ response: InterceptorStack<TransportResult<unknown, TRequest, TResponse>, RequestConfig<unknown, TRequest, TResponse>>
338
+ error: InterceptorStack<ResponseError<unknown, TRequest, TResponse>, RequestConfig<unknown, TRequest, TResponse>>
339
339
  }
340
340
 
341
341
  /**
@@ -426,8 +426,8 @@ function serializeUrl({
426
426
  /**
427
427
  * Creates a transport-agnostic interceptor channel that runs interceptors in registration order.
428
428
  */
429
- export function createInterceptorStack<T>(): InterceptorStack<T> {
430
- let entries: Array<{ id: number; fn: InterceptorFn<T> }> = []
429
+ export function createInterceptorStack<T, TContext = never>(): InterceptorStack<T, TContext> {
430
+ let entries: Array<{ id: number; fn: InterceptorFn<T, TContext> }> = []
431
431
  let counter = 0
432
432
  return {
433
433
  use(fn) {
@@ -442,10 +442,10 @@ export function createInterceptorStack<T>(): InterceptorStack<T> {
442
442
  const entry = entries.find((item) => item.id === id)
443
443
  if (entry) entry.fn = fn
444
444
  },
445
- async run(value) {
445
+ async run(value, context) {
446
446
  let result = value
447
447
  for (const entry of entries) {
448
- result = await entry.fn(result)
448
+ result = await entry.fn(result, context)
449
449
  }
450
450
  return result
451
451
  },
@@ -558,7 +558,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
558
558
  const { querySerializer, bodySerializer, pathSerializer } = resolveSerializers({ config, requestConfig })
559
559
  const codecs = { ...config.codecs, ...requestConfig.codecs }
560
560
 
561
- 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))
562
562
  const { request: requestContentTypeOption, response: responseContentType } = resolveContentType(requestConfig.contentType)
563
563
  const requestContentType = requestContentTypeOption ?? getHeader(headers, 'content-type')
564
564
  if (responseContentType && !hasHeader(headers, 'accept')) {
@@ -575,7 +575,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
575
575
  })
576
576
 
577
577
  if (requestConfig.cookies) {
578
- const cookie = serializeCookies(requestConfig.cookies, requestConfig.styles?.cookie)
578
+ const cookie = serializeCookies(requestConfig.cookies as Record<string, unknown>, requestConfig.styles?.cookie)
579
579
  if (cookie) headers['Cookie'] = [headers['Cookie'], cookie].filter(Boolean).join('; ')
580
580
  }
581
581
 
@@ -604,7 +604,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
604
604
 
605
605
  const url = serializeUrl({
606
606
  parts: [requestConfig.baseURL ?? config.baseURL, requestConfig.url],
607
- pathParams: requestConfig.path ?? {},
607
+ pathParams: (requestConfig.path ?? {}) as Record<string, unknown>,
608
608
  search: querySerializer(query, requestConfig.styles?.query),
609
609
  pathSerializer,
610
610
  pathStyles: requestConfig.styles?.path,
@@ -640,6 +640,7 @@ async function settleResult<TRequest, TResponse>({
640
640
  validator,
641
641
  onValidationError,
642
642
  errorInterceptors,
643
+ requestConfig,
643
644
  }: {
644
645
  result: TransportResult<unknown, TRequest, TResponse>
645
646
  request: ResolvedRequest
@@ -647,7 +648,8 @@ async function settleResult<TRequest, TResponse>({
647
648
  throwOnError: boolean
648
649
  validator: { response?: Validator; error?: Validator } | undefined
649
650
  onValidationError: ValidationErrorHandler | undefined
650
- errorInterceptors: InterceptorStack<ResponseError<unknown, TRequest, TResponse>>
651
+ errorInterceptors: InterceptorStack<ResponseError<unknown, TRequest, TResponse>, RequestConfig<unknown, TRequest, TResponse>>
652
+ requestConfig: RequestConfig<unknown, TRequest, TResponse>
651
653
  }): Promise<CallResult<TRequest, TResponse>> {
652
654
  const isSuccess = result.status >= 200 && result.status < 300
653
655
  const contentType = result.contentType ?? getResponseContentType(result.headers)
@@ -684,7 +686,7 @@ async function settleResult<TRequest, TResponse>({
684
686
  request: result.request,
685
687
  response: result.response,
686
688
  })
687
- await errorInterceptors.run(responseError)
689
+ await errorInterceptors.run(responseError, requestConfig)
688
690
  throw responseError
689
691
  }
690
692
  return { status: result.status, data: undefined, error, contentType, request: result.request, response: result.response }
@@ -700,17 +702,17 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
700
702
  let config: ClientConfig<TRequest, TResponse> = { ...initialConfig }
701
703
 
702
704
  const interceptors: Interceptors<TRequest, TResponse> = {
703
- request: createInterceptorStack<ResolvedRequest>(),
704
- response: createInterceptorStack<TransportResult<unknown, TRequest, TResponse>>(),
705
- error: createInterceptorStack<ResponseError<unknown, TRequest, TResponse>>(),
705
+ request: createInterceptorStack<ResolvedRequest, RequestConfig<unknown, TRequest, TResponse>>(),
706
+ response: createInterceptorStack<TransportResult<unknown, TRequest, TResponse>, RequestConfig<unknown, TRequest, TResponse>>(),
707
+ error: createInterceptorStack<ResponseError<unknown, TRequest, TResponse>, RequestConfig<unknown, TRequest, TResponse>>(),
706
708
  }
707
709
 
708
710
  const client = (async <TBody = unknown>(requestConfig: RequestConfig<TBody, TRequest, TResponse>): Promise<CallResult<TRequest, TResponse>> => {
709
711
  const transport = requestConfig.transport ?? config.transport ?? defaultTransport
710
712
  const { request, codecs } = await resolveRequest({ config, requestConfig })
711
713
 
712
- const resolvedRequest = await interceptors.request.run(request)
713
- const result = await interceptors.response.run(await transport(resolvedRequest))
714
+ const resolvedRequest = await interceptors.request.run(request, requestConfig)
715
+ const result = await interceptors.response.run(await transport(resolvedRequest), requestConfig)
714
716
 
715
717
  return settleResult({
716
718
  result,
@@ -720,6 +722,7 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
720
722
  validator: requestConfig.validator,
721
723
  onValidationError: requestConfig.onValidationError ?? config.onValidationError,
722
724
  errorInterceptors: interceptors.error,
725
+ requestConfig,
723
726
  })
724
727
  }) as ClientInstance<TRequest, TResponse>
725
728
 
@@ -733,7 +736,7 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
733
736
  const query: Record<string, unknown> = { ...((requestConfig.query ?? requestConfig.params) as Record<string, unknown> | undefined) }
734
737
  return serializeUrl({
735
738
  parts: [requestConfig.baseURL ?? config.baseURL, requestConfig.url],
736
- pathParams: requestConfig.path ?? {},
739
+ pathParams: (requestConfig.path ?? {}) as Record<string, unknown>,
737
740
  search: querySerializer(query, requestConfig.styles?.query),
738
741
  pathSerializer,
739
742
  pathStyles: requestConfig.styles?.path,