@kubb/plugin-fetch 5.7.1 → 5.7.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.
@@ -22,10 +22,14 @@ export type SerializationStyle<TStyle = string> = {
22
22
  explode?: boolean;
23
23
  allowReserved?: boolean;
24
24
  };
25
+ /** The media type of a parameter described with `content`; JSON sends one JSON-encoded value. */
26
+ export type ParamContentType = {
27
+ contentType?: string;
28
+ };
25
29
  /**
26
30
  * The per-parameter query serialization metadata carried by the generated request.
27
31
  */
28
- export type QueryParamStyle = SerializationStyle<QueryStyle>;
32
+ export type QueryParamStyle = SerializationStyle<QueryStyle> & ParamContentType;
29
33
  /**
30
34
  * Serializes the query object into a search string. The optional second argument carries the
31
35
  * per-parameter OpenAPI `style` / `explode` / `allowReserved` metadata; without it arrays explode
@@ -36,7 +40,7 @@ export type QuerySerializer = (params: Record<string, unknown>, options?: Record
36
40
  * The per-parameter cookie serialization metadata carried by the generated request. Cookies use the
37
41
  * OpenAPI `form` style, so only `explode` is configurable.
38
42
  */
39
- export type CookieParamStyle = {
43
+ export type CookieParamStyle = ParamContentType & {
40
44
  explode?: boolean;
41
45
  };
42
46
  /**
@@ -49,7 +53,7 @@ export type CookieParamStyle = {
49
53
  * // styles.header: { 'X-Filter': { explode: true } }, header { role: 'admin' } -> 'X-Filter: role=admin'
50
54
  * ```
51
55
  */
52
- export type HeaderParamStyle = {
56
+ export type HeaderParamStyle = ParamContentType & {
53
57
  explode?: boolean;
54
58
  };
55
59
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-fetch",
3
- "version": "5.7.1",
3
+ "version": "5.7.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",
@@ -80,17 +80,17 @@
80
80
  "registry": "https://registry.npmjs.org/"
81
81
  },
82
82
  "dependencies": {
83
- "@kubb/plugin-ts": "^5.0.15",
84
- "@kubb/plugin-zod": "^5.4.13"
83
+ "@kubb/plugin-ts": "^5.0.16",
84
+ "@kubb/plugin-zod": "^5.5.0"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@internals/client": "0.0.0",
88
88
  "@internals/shared": "0.0.0",
89
- "kubb": "^5.12.5",
89
+ "kubb": "^5.12.8",
90
90
  "typescript": "^7.0.2"
91
91
  },
92
92
  "peerDependencies": {
93
- "kubb": "^5.12.5"
93
+ "kubb": "^5.12.8"
94
94
  },
95
95
  "engines": {
96
96
  "node": ">=22.12"
package/src/plugin.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path'
2
2
  import { createSdkGenerator, defaultMacros, isValidatorEnabled, resolverClient } from '@internals/client'
3
3
  import { createGroupConfig } from '@internals/shared'
4
- import { addGenerator, assertPluginOption, defineExtension, injectFile, Resolver, setMacros, setResolver, useConfig } from 'kubb/kit'
4
+ import { addGenerator, defineExtension, injectFile, Resolver, setMacros, setResolver, useConfig } from 'kubb/kit'
5
5
  import { compatibility } from '../kubb-extension.json'
6
6
  import { version } from '../package.json'
7
7
  import { pluginTsName } from '@kubb/plugin-ts'
@@ -74,27 +74,6 @@ export const pluginFetch = defineExtension<PluginFetch>({
74
74
  resolver: userResolver ? Resolver.merge<ResolverClient>(resolverClient, userResolver) : resolverClient,
75
75
  }
76
76
 
77
- const allowedReturnTypes = ['full', 'data', 'plain'] satisfies Array<ResolvedOptions['returnType']>
78
- assertPluginOption({ plugin: pluginFetchName, option: 'returnType', value: returnType, allowed: allowedReturnTypes })
79
- // An override replaces `returnType` per operation, so its values are checked too.
80
- for (const [index, entry] of override.entries()) {
81
- if (entry.options.returnType === undefined) continue
82
- assertPluginOption({
83
- plugin: pluginFetchName,
84
- option: `override[${index}].options.returnType`,
85
- value: entry.options.returnType,
86
- allowed: allowedReturnTypes,
87
- })
88
- }
89
- if (resolved.sdk) {
90
- assertPluginOption({
91
- plugin: pluginFetchName,
92
- option: 'sdk.mode',
93
- value: resolved.sdk.mode,
94
- allowed: ['tag', 'flat'] satisfies Array<NonNullable<ResolvedOptions['sdk']>['mode']>,
95
- })
96
- }
97
-
98
77
  setResolver(resolved.resolver)
99
78
  setMacros([...defaultMacros, ...(options.macros ?? [])])
100
79
 
@@ -25,10 +25,15 @@ export type SerializationStyle<TStyle = string> = {
25
25
  allowReserved?: boolean
26
26
  }
27
27
 
28
+ /** The media type of a parameter described with `content`; JSON sends one JSON-encoded value. */
29
+ export type ParamContentType = {
30
+ contentType?: string
31
+ }
32
+
28
33
  /**
29
34
  * The per-parameter query serialization metadata carried by the generated request.
30
35
  */
31
- export type QueryParamStyle = SerializationStyle<QueryStyle>
36
+ export type QueryParamStyle = SerializationStyle<QueryStyle> & ParamContentType
32
37
 
33
38
  /**
34
39
  * Serializes the query object into a search string. The optional second argument carries the
@@ -41,7 +46,7 @@ export type QuerySerializer = (params: Record<string, unknown>, options?: Record
41
46
  * The per-parameter cookie serialization metadata carried by the generated request. Cookies use the
42
47
  * OpenAPI `form` style, so only `explode` is configurable.
43
48
  */
44
- export type CookieParamStyle = {
49
+ export type CookieParamStyle = ParamContentType & {
45
50
  explode?: boolean
46
51
  }
47
52
 
@@ -55,7 +60,7 @@ export type CookieParamStyle = {
55
60
  * // styles.header: { 'X-Filter': { explode: true } }, header { role: 'admin' } -> 'X-Filter: role=admin'
56
61
  * ```
57
62
  */
58
- export type HeaderParamStyle = {
63
+ export type HeaderParamStyle = ParamContentType & {
59
64
  explode?: boolean
60
65
  }
61
66
 
@@ -195,6 +200,11 @@ function serializeUrlencodedBody(body: Record<string, unknown>, encoding: Record
195
200
  return parts.join('&')
196
201
  }
197
202
 
203
+ function isJsonMediaType(contentType: string | undefined): boolean {
204
+ const type = contentType?.split(';')[0]?.trim().toLowerCase()
205
+ return type === 'application/json' || !!type?.endsWith('+json')
206
+ }
207
+
198
208
  function serializeCookie({ name, value, explode }: { name: string; value: unknown; explode: boolean }): string {
199
209
  if (Array.isArray(value)) {
200
210
  const items = value.filter(notNullish).map((item) => encodeURIComponent(toValue(item)))
@@ -225,7 +235,12 @@ export function serializeCookies(cookies: Record<string, unknown>, styles?: Reco
225
235
  const parts: Array<string> = []
226
236
  for (const [name, value] of Object.entries(cookies)) {
227
237
  if (value === undefined || value === null) continue
228
- parts.push(serializeCookie({ name, value, explode: styles?.[name]?.explode ?? false }))
238
+ const style = styles?.[name]
239
+ if (isJsonMediaType(style?.contentType)) {
240
+ parts.push(`${name}=${encodeURIComponent(JSON.stringify(value, jsonReplacer))}`)
241
+ continue
242
+ }
243
+ parts.push(serializeCookie({ name, value, explode: style?.explode ?? false }))
229
244
  }
230
245
  return parts.join('; ')
231
246
  }
@@ -368,6 +383,10 @@ export const defaultQuerySerializer: QuerySerializer = (params, options) => {
368
383
  const parts: Array<string> = []
369
384
  for (const [key, value] of Object.entries(params)) {
370
385
  const paramOptions = options?.[key]
386
+ if (notNullish(value) && isJsonMediaType(paramOptions?.contentType)) {
387
+ parts.push(`${encodeComponent(key)}=${encodeComponent(JSON.stringify(value, jsonReplacer))}`)
388
+ continue
389
+ }
371
390
  parts.push(...(paramOptions ? serializeStyledQueryParam({ key, value, options: paramOptions }) : serializeDefaultQueryParam(key, value)))
372
391
  }
373
392
  return parts.join('&')
@@ -429,6 +448,11 @@ function serializeHeaderValue(value: unknown, explode: boolean): string {
429
448
  .join(',')
430
449
  }
431
450
 
451
+ /** Header values must be Latin-1, so non-ASCII characters become JSON `\uXXXX` escapes. */
452
+ function toAsciiJson(value: unknown): string {
453
+ return JSON.stringify(value, jsonReplacer).replace(/[\u007f-\uffff]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`)
454
+ }
455
+
432
456
  /**
433
457
  * Serializes array and object header parameters with the OpenAPI `simple` style before they are
434
458
  * merged. Header values are not URL-encoded. Primitive values and headers without metadata pass
@@ -439,7 +463,9 @@ export function applyHeaderStyles(headers: HeadersInit | undefined, styles: Reco
439
463
  const entries = Array.isArray(headers) ? headers : Object.entries(headers)
440
464
  return entries.map(([key, value]) => {
441
465
  const style = styles[key]
442
- if (!style || value === undefined || value === null || typeof value !== 'object') return [key, value] as [string, HeaderValue]
466
+ if (!style || value === undefined || value === null) return [key, value] as [string, HeaderValue]
467
+ if (isJsonMediaType(style.contentType)) return [key, toAsciiJson(value)] as [string, HeaderValue]
468
+ if (typeof value !== 'object') return [key, value] as [string, HeaderValue]
443
469
  return [key, serializeHeaderValue(value, style.explode ?? false)] as [string, HeaderValue]
444
470
  })
445
471
  }