@kubb/plugin-fetch 5.14.0-canary.20261109T125041 → 6.0.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.
@@ -7,7 +7,7 @@ Each event on the wire has up to four fields, and a blank line ends it:
7
7
  | Field | Meaning |
8
8
  | ------- | -------------------------------------------------------------------------------- |
9
9
  | `event` | The event name. `message` when the server sends none. |
10
- | `data` | The payload. Parsed as JSON unless the document types it as a string. |
10
+ | `data` | The payload. Parsed as JSON when it is valid JSON, and kept as text otherwise. |
11
11
  | `id` | The last event ID. It carries over to later events and resumes a dropped stream. |
12
12
  | `retry` | The reconnect delay in milliseconds. |
13
13
 
@@ -33,7 +33,7 @@ For one payload on every event, such as a feed of pet changes. The item declares
33
33
 
34
34
  ### Describe it
35
35
 
36
- In OpenAPI 3.2, put one `oneOf` member per event name on `itemSchema`, with a `const` on `event`. Describe JSON inside `data` with `contentMediaType` and `contentSchema`:
36
+ In OpenAPI 3.2, put one `oneOf` member per event name on `itemSchema`, with a `const` on `event`. Point `data` at the payload schema with `$ref`. A `data` string described with `contentMediaType: application/json` and `contentSchema` is typed and parsed as that schema too:
37
37
 
38
38
  ```yaml
39
39
  /chat:
@@ -53,13 +53,16 @@ In OpenAPI 3.2, put one `oneOf` member per event name on `itemSchema`, with a `c
53
53
  id: { type: string }
54
54
  retry: { type: integer, minimum: 0 }
55
55
  oneOf:
56
- - properties:
57
- event: { const: message_delta }
58
- data:
59
- contentMediaType: application/json
60
- contentSchema: { $ref: '#/components/schemas/MessageDelta' }
61
- - properties:
62
- event: { const: ping }
56
+ - type: object
57
+ required: [event, data]
58
+ properties:
59
+ event: { type: string, const: message_delta }
60
+ data: { $ref: '#/components/schemas/MessageDelta' }
61
+ - type: object
62
+ required: [event]
63
+ properties:
64
+ event: { type: string, const: ping }
65
+ data: { type: string }
63
66
  ```
64
67
 
65
68
  In OpenAPI 3.0 and 3.1, put the same `oneOf` on `schema`, with a `discriminator` on `event`. Kubb reads `schema` as one event.
@@ -173,16 +176,63 @@ for await (const { data } of stream) {
173
176
  }
174
177
  ```
175
178
 
179
+ ## JSON Lines
180
+
181
+ An operation whose only success content type is a JSON sequence, such as `application/jsonl`, `application/x-ndjson`, or `application/json-seq`, streams one item per line. Each line is parsed as JSON, so the loop gets each item as soon as its line arrives instead of waiting for the whole body. An `application/json-seq` record must fit on one line; a record that spans several lines fails to parse.
182
+
183
+ ### Describe it
184
+
185
+ Point `itemSchema` at the schema of one line:
186
+
187
+ ```yaml
188
+ /logs:
189
+ get:
190
+ operationId: streamLogs
191
+ responses:
192
+ '200':
193
+ description: One LogEntry per line
194
+ content:
195
+ application/jsonl:
196
+ itemSchema:
197
+ $ref: '#/components/schemas/LogEntry'
198
+ ```
199
+
200
+ In OpenAPI 3.0 and 3.1, `schema` on one of these media types describes one line.
201
+
202
+ ### The stream
203
+
204
+ ```text
205
+ {"level": "info", "message": "started"}
206
+ {"level": "warn", "message": "slow"}
207
+ ```
208
+
209
+ The call asks for the declared media type in `Accept`. A line may end with CRLF, CR, or LF, blank lines are skipped, and a last line without a line break still counts. A line that is not valid JSON throws a `SyntaxError` from the loop.
210
+
211
+ ### Use it
212
+
213
+ Each item is a `LogEntry`, with no event wrapper:
214
+
215
+ ```typescript
216
+ import { streamLogs } from './gen/clients/streamLogs'
217
+
218
+ for await (const entry of await streamLogs()) {
219
+ console.info(entry.level, entry.message) // LogEntry
220
+ }
221
+ ```
222
+
223
+ `terminator`, `maxEventSize`, and `signal` work per line. `transform` only applies to Server-Sent Events. With `validator: 'zod'`, the validator runs on each line. `lastEventId` stays empty, since JSON Lines has no event IDs.
224
+
176
225
  ## Stream options
177
226
 
178
227
  Pass these on the call, next to its request params:
179
228
 
180
- | Option | What it does |
181
- | -------------- | ------------------------------------------------------------------------------------------------------------------------------- |
182
- | `terminator` | Ends the stream when `data` equals this value, such as `[DONE]`. Without it, the sentinel reaches your loop. |
183
- | `transform` | Maps each event before validation, for example to camelCase its keys. |
184
- | `maxEventSize` | Largest event in characters. A larger one throws a `RangeError`. Defaults to `16 * 1024 * 1024`. Set `Infinity` to turn it off. |
185
- | `signal` | An `AbortSignal` that ends the request and the loop. |
229
+ | Option | What it does |
230
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
231
+ | `terminator` | Ends the stream when `data` equals this value, such as `[DONE]`. Without it, the sentinel reaches your loop, or fails the `validator` when it does not match the event schema. |
232
+ | `transform` | Maps each event before validation, for example to camelCase its keys. |
233
+ | `maxEventSize` | Largest event in characters. A larger one throws a `RangeError`. Defaults to `16 * 1024 * 1024`. Set `Infinity` to turn it off. |
234
+ | `signal` | An `AbortSignal` that ends the request and the loop. |
235
+ | `eventShape` | What the validator checks: `data` for the payload, `event` for the whole event. Generated calls set it. |
186
236
 
187
237
  To stop reading, `break` out of the loop. The runtime then cancels the reader and releases the connection.
188
238
 
@@ -202,6 +252,16 @@ const { stream } = await streamChat({
202
252
 
203
253
  Set `validate: false` on the call or the client to skip it. See [Handle errors](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/error-handling) for the validation options.
204
254
 
255
+ ## Read the response headers
256
+
257
+ A stream result carries the response `headers`, as a regular result does. When the response declares headers, such as an `X-Request-ID`, the call returns `StreamResultWithHeaders`, so `headers.get` suggests their names for the narrowed `status`:
258
+
259
+ ```typescript
260
+ const result = await streamChat({ body: { prompt: 'Hi' } })
261
+
262
+ console.info(result.headers.get('X-Request-ID'))
263
+ ```
264
+
205
265
  ## Resume after a drop
206
266
 
207
267
  A dropped connection ends the loop with the transport's error. The result's `lastEventId` is the `id` of the last event the server completed, so a new call with a `Last-Event-ID` header continues where the stream stopped. The runtime does not reconnect by itself, because many streams, such as a chat completion, end on purpose, and the retry policy belongs to your app:
@@ -239,7 +299,7 @@ try {
239
299
  }
240
300
  ```
241
301
 
242
- With `throwOnError: false`, the call resolves instead of throwing, as a regular call does. The result then carries the `status` and the `error` body, its `data` is `undefined`, and its stream yields nothing. Check `data` rather than `error`, because an empty error body leaves `error` undefined. Checking `status` narrows `error` to the body the document declares for that status:
302
+ With `throwOnError: false`, the call resolves instead of throwing, as a regular call does. The result then carries the `status` and the `error` body, its `data` is `undefined`, and its stream yields nothing. Check `data` to tell the two apart. An empty error body sets `error` to `{ status, statusText }`. Checking `status` narrows `error` to the body the document declares for that status:
243
303
 
244
304
  ```typescript
245
305
  const result = await streamChat({ body, throwOnError: false })
@@ -253,10 +313,51 @@ As on a regular call, `.unwrap()` resolves to the events. A failed call rejects
253
313
  for await (const event of await streamChat({ body }).unwrap()) render(event)
254
314
  ```
255
315
 
256
- A stream call that gets a JSON response, such as an error body, parses it like any other call, so a 2xx JSON body yields an empty stream. An operation that declares another content type next to `text/event-stream` is generated as a normal call.
316
+ A stream call that gets a JSON error body parses it like any other call. A 2xx answer that is not an event stream, such as a JSON body, throws a `TypeError` with the body as its `cause`, and an empty 2xx answer ends as an empty stream. An operation that declares another content type next to `text/event-stream` or a JSON Lines type is a normal call. See [JSON or an event stream](#json-or-an-event-stream).
257
317
 
258
318
  Declare heartbeat and `error` events in the document, so each has a `case` in your `switch`.
259
319
 
320
+ ## JSON or an event stream
321
+
322
+ Some APIs, such as Wikimedia EventStreams, declare the same response as `application/json` and as `text/event-stream`. Kubb generates a normal call for it, which returns a `RequestResult`. The call sends `Accept: text/event-stream`, so the server streams events. To ask for JSON, pass `contentType: { response: 'application/json' }`. Wikimedia answers JSON with an endless JSON Lines body, so also pass `parseAs: 'stream'` and read `result.data` with `parseJsonLines({ stream: result.data })` from `.kubb/client`. Without it, the call waits for a body that never ends.
323
+
324
+ Check `contentType` on the result. For `text/event-stream`, `data` is the raw `ReadableStream<Uint8Array>`. Pass it to `parseEventStream` from `.kubb/client` to read the events:
325
+
326
+ ```typescript
327
+ import { parseEventStream } from './gen/.kubb/client'
328
+ import { getV2StreamRecentchange } from './gen/clients/getV2StreamRecentchange'
329
+
330
+ const result = await getV2StreamRecentchange({ signal })
331
+
332
+ if (result.contentType === 'text/event-stream') {
333
+ for await (const { data } of parseEventStream(result.data)) render(data)
334
+ } else {
335
+ render(result.data) // the JSON body
336
+ }
337
+ ```
338
+
339
+ To get a stream result with `lastEventId`, send the same request with `client.stream`. It throws a `TypeError` when the server answers with JSON:
340
+
341
+ ```typescript
342
+ import { client } from './gen/.kubb/client'
343
+
344
+ const result = await client.stream({ method: 'GET', url: '/v2/stream/recentchange', eventShape: 'data', signal })
345
+ for await (const event of result) render(event.data)
346
+ ```
347
+
348
+ ## Stream with the client
349
+
350
+ `client.stream(config)` runs the same interceptors and transport as `client(config)`. On a 2xx answer, it reads the body item by item and validates each item with `validator.response`. A non-2xx body is decoded whole, so `error` stays a value. Type it with the responses record, `ThrowOnError`, and the item type, which defaults to `ServerSentEvent` around the success body:
351
+
352
+ ```typescript
353
+ import { client, type SuccessOf } from './gen/.kubb/client'
354
+ import type { StreamLogsResponses } from './gen/models/StreamLogs'
355
+
356
+ const result = await client.stream<StreamLogsResponses, true, SuccessOf<StreamLogsResponses>>({ method: 'GET', url: '/logs' })
357
+ ```
358
+
359
+ To stream another format, register a `stream` handler, as [Configure serialization](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/serialization#add-a-media-type) shows.
360
+
260
361
  ## Return only the events
261
362
 
262
363
  With `returnType: 'data'` in the plugin options, a stream call returns its events, as a regular call returns only its body:
@@ -269,16 +370,16 @@ pluginFetch({ returnType: 'data' })
269
370
 
270
371
  Both modes read the same events. The table compares them for one call:
271
372
 
272
- | | `returnType: 'full'` (default) | `returnType: 'data'` |
273
- | ----------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- |
274
- | The call returns | `Unwrappable<StreamResult<…>>` | `Promise<UnwrappedStreamResult<…>>` |
275
- | Call it | `const result = await streamPetEvents({ path: { petId: 1 } })` | `const events = await streamPetEvents({ path: { petId: 1 } })` |
276
- | Loop over the events | `for await (const event of result.stream)` | `for await (const event of events)` |
277
- | Other ways to loop | `result`, `result.data`, or `.unwrap()` | none |
278
- | `event.data` | `PetEvent` | `PetEvent` |
279
- | A failed call, by default | throws a `ResponseError` | throws a `ResponseError` |
280
- | With `throwOnError: false` | the full result | the full result, as in `'full'` |
281
- | `status`, `response`, `lastEventId` | available | only with `throwOnError: false` |
373
+ | | `returnType: 'full'` (default) | `returnType: 'data'` |
374
+ | ---------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- |
375
+ | The call returns | `Unwrappable<StreamResult<…>>` | `Promise<UnwrappedStreamResult<…>>` |
376
+ | Call it | `const result = await streamPetEvents({ path: { petId: 1 } })` | `const events = await streamPetEvents({ path: { petId: 1 } })` |
377
+ | Loop over the events | `for await (const event of result.stream)` | `for await (const event of events)` |
378
+ | Other ways to loop | `result`, `result.data`, or `.unwrap()` | none |
379
+ | `event.data` | `PetEvent` | `PetEvent` |
380
+ | A failed call, by default | throws a `ResponseError` | throws a `ResponseError` |
381
+ | With `throwOnError: false` | the full result | the full result, as in `'full'` |
382
+ | `status`, `headers`, `response`, `lastEventId` | available | only with `throwOnError: false` |
282
383
 
283
384
  Loop over the events the call resolves to:
284
385
 
@@ -323,7 +424,7 @@ The events alone carry no `response` or `lastEventId`. To resume a dropped strea
323
424
 
324
425
  ## The runtime files
325
426
 
326
- `.kubb/sse.ts` holds the parser, `toEventStream`, and the `ServerSentEvent`, `TypedEvent`, `EventStreamOptions`, and `EventStreamResult` types. A stream operation takes `StreamOptions` and returns `StreamResult`, the stream versions of `Options` and `RequestResult`. Kubb writes it only when the document has a `text/event-stream` operation, and `.kubb/client.ts` then re-exports it, so the types import from either file. Kubb regenerates both on each run, so do not edit them:
427
+ `.kubb/sse.ts` holds `parseEventStream`, `parseJsonLines`, and the `eventStream`, `jsonLines`, and `streamMediaTypes` media types. The `ServerSentEvent`, `TypedEvent`, `EventStreamOptions`, `StreamOptions`, `StreamResult`, and `StreamResultWithHeaders` types live in `.kubb/core.ts`. A stream operation takes `StreamOptions` and returns `StreamResult`, or `StreamResultWithHeaders` when the response declares headers, the stream versions of `Options`, `RequestResult`, and `RequestResultWithHeaders`. `.kubb/client.ts` re-exports `sse.ts`, so everything imports from the client. Kubb regenerates these files on each run, so do not edit them:
327
428
 
328
429
  ```typescript
329
430
  import type { ServerSentEvent } from './gen/.kubb/client'
@@ -6,10 +6,10 @@ The transport is a function. Use interceptors for request headers or response lo
6
6
 
7
7
  ## Write a transport function
8
8
 
9
- `@kubb/plugin-fetch` types the transport as a function that receives a fully resolved request and returns a result:
9
+ `@kubb/plugin-fetch` types the transport as a function that receives a fully resolved request and returns a web `Response`:
10
10
 
11
11
  ```typescript
12
- type Transport = (request: ResolvedRequest) => Promise<TransportResult>
12
+ type Transport = (request: ResolvedRequest) => Promise<Response | TransportResult>
13
13
 
14
14
  type ResolvedRequest = {
15
15
  url: string
@@ -18,22 +18,37 @@ type ResolvedRequest = {
18
18
  body?: RequestBody
19
19
  signal?: AbortSignal
20
20
  credentials?: RequestCredentials
21
- options?: FetchOptions
22
- responseType?: ResponseType
21
+ options?: NativeOptions
22
+ // Set when the call streams the response. Do not buffer the body.
23
+ stream?: boolean
23
24
  }
24
25
 
25
- type TransportResult<TData = unknown> = {
26
- data: TData
27
- status: number
28
- statusText: string
29
- headers: Headers
30
- contentType?: string
26
+ type TransportResult = {
27
+ webResponse: Response
31
28
  request: Request
32
29
  response: Response
33
30
  }
34
31
  ```
35
32
 
36
- The core hands you a `ResolvedRequest` with the URL built, the query and body serialized, and auth headers set. Return the parsed `data` along with the native `request` and `response`, so status, headers, and the raw body stay reachable on the result.
33
+ The `ResolvedRequest` has the URL built, the query and body encoded, and auth headers set. Return the `Response` with its body unread. Return a `TransportResult` to set the native `request` and `response` on results and errors.
34
+
35
+ ## Send with another library
36
+
37
+ A library that returns a `Response`, such as [ky](https://github.com/sindresorhus/ky), needs only the send. `toRequestInit(request)` gives the `RequestInit` that `fetch` and ky take:
38
+
39
+ ```typescript
40
+ import ky from 'ky'
41
+ import { client, toRequestInit } from './gen/.kubb/client'
42
+
43
+ client.setConfig({
44
+ // The client splits success from error itself, so ky must not throw or retry on an error status. ky's `timeout`
45
+ // (10 seconds by default) only waits for the response headers and never ends a started body or stream. Turn it
46
+ // off and bound the whole call with `signal: AbortSignal.timeout(ms)`.
47
+ transport: (request) => ky(request.url, { ...toRequestInit(request), throwHttpErrors: false, retry: 0, timeout: false }),
48
+ })
49
+ ```
50
+
51
+ The library must hand back the body unread. `result.request` is then a `Request` built from the resolved request, without its body, since the library keeps its own. With a relative URL, it is the resolved request itself.
37
52
 
38
53
  ## Mock the network in tests
39
54
 
@@ -44,14 +59,7 @@ import { createClient } from './gen/.kubb/client'
44
59
  import { getPetById } from './gen/clients/getPetById'
45
60
 
46
61
  const testClient = createClient({
47
- transport: async (request) => ({
48
- data: { id: 1, name: 'Fluffy' },
49
- status: 200,
50
- statusText: 'OK',
51
- headers: new Headers(),
52
- request: new Request(request.url),
53
- response: new Response(),
54
- }),
62
+ transport: async () => Response.json({ id: 1, name: 'Fluffy' }),
55
63
  })
56
64
 
57
65
  const { data } = await getPetById({ path: { petId: 1 }, client: testClient })
package/docs/options.md CHANGED
@@ -23,7 +23,7 @@ Pass these options to `pluginFetch()`. Shared options link to [Shared plugin opt
23
23
 
24
24
  ### baseURL
25
25
 
26
- Base URL prepended to every request. When omitted, each request uses the operation's relative path from the spec. A value with a `${...}` interpolation is emitted as a template literal in the generated client config, so `baseURL: '${process.env.API_URL}'` reads the environment variable at runtime.
26
+ Base URL prepended to every request. When omitted, each request uses the operation's relative path from the spec. A path or operation that declares its own `servers` uses that URL instead. See [Set the base URL](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/base-url). A value with a `${...}` interpolation is emitted as a template literal in the generated client config, so `baseURL: '${process.env.API_URL}'` reads the environment variable at runtime.
27
27
 
28
28
  | | |
29
29
  | -------- | -------- |
@@ -63,7 +63,7 @@ Default value. Does no validation and returns the response cast to the generated
63
63
  :::
64
64
 
65
65
  :::field{name="'zod'"}
66
- Validates the success response body, and the error body when a non-2xx call does not throw.
66
+ Validates the success response body, and the error body against the schema of its status.
67
67
  :::
68
68
 
69
69
  :::field{name="{ request?: 'zod', params?: 'zod', response?: 'zod' }"}
@@ -107,7 +107,7 @@ Generates a class-based SDK. Each instance takes its own client config, so envir
107
107
  ::field-group
108
108
 
109
109
  :::field{name="'tag'"}
110
- Default value for `sdk.mode`. Generates one class per tag, such as `PetClient`. Set `sdk.name` to also get a root class that shares one config, used as `new PetStore(config).pet.getPetById(...)`.
110
+ Default value for `sdk.mode`. Generates one class per tag, such as `PetClient`. Set `sdk.name` to also get a root class that shares one client with every tag, used as `new PetStore(config).pet.getPetById(...)`.
111
111
  :::
112
112
 
113
113
  :::field{name="'flat'"}
@@ -125,6 +125,16 @@ const pet = new PetClient({ baseURL: 'https://petstore.swagger.io/v2' })
125
125
  const { data } = await pet.getPetById({ path: { petId: 1 } })
126
126
  ```
127
127
 
128
+ Each SDK class exposes its client as the read-only `client` field. Interceptors on the root class's `client` apply to every tag:
129
+
130
+ ```typescript
131
+ const api = new PetStore({ baseURL: 'https://petstore.swagger.io/v2' })
132
+ api.client.interceptors.request.use((request) => request)
133
+ await api.pet.getPetById({ path: { petId: 1 } })
134
+ ```
135
+
136
+ A tag class also accepts an existing client, as in `new PetClient(api.client)`.
137
+
128
138
  ### returnType
129
139
 
130
140
  Shape of the value a generated call resolves to.
@@ -138,7 +148,7 @@ Shape of the value a generated call resolves to.
138
148
  ::field-group
139
149
 
140
150
  :::field{name="'full'"}
141
- Default value. Returns `{ status, data, error, contentType, request, response }`.
151
+ Default value. Returns `{ status, data, error, contentType, headers, request, response }`. `headers` is the response's `Headers`. For an operation whose responses declare `headers`, its `get` suggests those names once `status` is narrowed, and the values stay strings.
142
152
  :::
143
153
 
144
154
  :::field{name="'data'"}
package/docs/overview.md CHANGED
@@ -12,7 +12,7 @@ The query plugins (`@kubb/plugin-react-query`, `@kubb/plugin-vue-query`, and `@k
12
12
 
13
13
  ## Options worth knowing
14
14
 
15
- - `baseURL` is prepended to every request. The spec's `servers` URL is not used, so without `baseURL` you set one at runtime with `client.setConfig({ baseURL })`.
15
+ - `baseURL` is prepended to every request. The spec's top-level `servers` URL is not used, so without `baseURL` you set one at runtime with `client.setConfig({ baseURL })`. Operations with their own `servers`, on the path or the operation, call that URL instead.
16
16
  - `validator: 'zod'` validates response bodies with the schemas from `@kubb/plugin-zod`. Use `{ request: 'zod', response: 'zod' }` to also validate request bodies.
17
17
  - `throwOnErrorDefault: false` returns non-2xx responses as values instead of throwing. A single call can still pass `throwOnError`.
18
18
  - `returnType: 'data'` makes a call resolve to the success body instead of the full result. With `throwOnError: false`, the call still returns the full result.
@@ -20,7 +20,7 @@
20
20
  "dependencies": ["plugin-ts"],
21
21
  "optionalDependencies": ["plugin-zod"],
22
22
  "compatibility": {
23
- "kubb": ">=5.16.0",
23
+ "kubb": ">=5.18.4",
24
24
  "node": ">=22.22"
25
25
  },
26
26
  "role": "client",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-fetch",
3
- "version": "5.14.0-canary.20261109T125041",
3
+ "version": "6.0.0",
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",
@@ -39,6 +39,10 @@
39
39
  "src",
40
40
  "dist",
41
41
  "templates",
42
+ "!templates/config.ts",
43
+ "!templates/core.ts",
44
+ "!templates/serializers.ts",
45
+ "!templates/standardSchema.ts",
42
46
  "kubb-extension.json",
43
47
  "docs",
44
48
  "!/**/**.test.**",
@@ -67,8 +71,7 @@
67
71
  "./package.json": "./package.json",
68
72
  "./kubb-extension.json": "./kubb-extension.json",
69
73
  "./templates/*.ts": {
70
- "types": "./dist/templates/*.d.ts",
71
- "default": "./templates/*.ts"
74
+ "types": "./dist/templates/*.d.ts"
72
75
  },
73
76
  "./templates/*.js": {
74
77
  "types": "./dist/templates/*.d.ts"
@@ -80,15 +83,10 @@
80
83
  "registry": "https://registry.npmjs.org/"
81
84
  },
82
85
  "dependencies": {
83
- "@kubb/kit": "^5.16.0",
84
- "@kubb/plugin-client": "^5.12.0-canary.20261109T125041",
85
- "@kubb/plugin-ts": "^5.5.0-canary.20261109T125041",
86
- "@kubb/plugin-zod": "^5.9.0-canary.20261109T125041",
87
- "@kubb/renderer-jsx": "^5.16.0"
86
+ "@kubb/kit": "^5.18.6",
87
+ "@kubb/plugin-client": "^6.0.0"
88
88
  },
89
89
  "devDependencies": {
90
- "@internals/client": "0.1.0-canary.20261109T125041",
91
- "@internals/shared": "0.1.0-canary.20261109T125041",
92
90
  "typescript": "^7.0.2"
93
91
  },
94
92
  "peerDependencies": {},
package/src/index.ts CHANGED
@@ -1,3 +1,2 @@
1
- export { clientGenerator } from './generators/clientGenerator.tsx'
2
1
  export { default, pluginFetch, pluginFetchName } from './plugin.ts'
3
2
  export type { Options, PluginFetch, ResolvedOptions, ResolverClient } from './types.ts'
package/src/plugin.ts CHANGED
@@ -1,14 +1,9 @@
1
- import path from 'node:path'
2
- import { createSdkGenerator, defaultMacros, isValidatorEnabled, resolverClient } from '@internals/client'
3
- import { createGroupConfig } from '@internals/shared'
4
- import { addGenerator, defineExtension, injectFile, Resolver, setMacros, setResolver, useConfig } from '@kubb/kit'
1
+ import { fileURLToPath } from 'node:url'
2
+ import { defineExtension } from '@kubb/kit'
3
+ import { clientDependencies, resolveClientOptions, setupClient } from '@kubb/plugin-client'
5
4
  import { compatibility } from '../kubb-extension.json'
6
5
  import { version } from '../package.json'
7
- import { pluginTsName } from '@kubb/plugin-ts'
8
- import { pluginZodName } from '@kubb/plugin-zod'
9
- import { clientGenerator } from './generators/clientGenerator.tsx'
10
- import { fetchClientTemplatePath, coreTemplatePath, serializersTemplatePath, sseTemplatePath, standardSchemaTemplatePath } from './templates.ts'
11
- import type { PluginFetch, ResolvedOptions, ResolverClient } from './types.ts'
6
+ import type { PluginFetch } from './types.ts'
12
7
 
13
8
  /**
14
9
  * Canonical plugin name for `@kubb/plugin-fetch`. Used for driver lookups and cross-plugin
@@ -41,72 +36,10 @@ export const pluginFetchName = 'plugin-fetch' satisfies PluginFetch['name']
41
36
  export const pluginFetch = defineExtension<PluginFetch>({
42
37
  kind: 'plugin',
43
38
  meta: { name: pluginFetchName, version, compatibility },
44
- // `plugin-ts` supplies the operation types unless it is absent, in which case `pluginZod({ inferred: true })`
45
- // does. `plugin-zod` is required once `validator` enables a direction, so a missing one fails setup instead of codegen.
46
- dependencies: (options) => ({ [pluginTsName]: { optional: true }, [pluginZodName]: { optional: !isValidatorEnabled(options.validator) } }),
39
+ dependencies: clientDependencies,
47
40
  setup(options) {
48
- const config = useConfig()
49
- const {
50
- output = { path: 'clients', barrel: { type: 'named' } },
51
- exclude = [],
52
- include,
53
- override = [],
54
- baseURL,
55
- throwOnErrorDefault = true,
56
- validator = false,
57
- returnType = 'full',
58
- group,
59
- sdk,
60
- resolver: userResolver,
61
- } = options
62
-
63
- const resolved: ResolvedOptions = {
64
- output,
65
- exclude,
66
- include,
67
- override,
68
- group: createGroupConfig(group),
69
- baseURL,
70
- throwOnErrorDefault,
71
- validator,
72
- returnType,
73
- sdk: sdk ? { mode: sdk.mode ?? 'tag', name: sdk.name } : undefined,
74
- resolver: userResolver ? Resolver.merge<ResolverClient>(resolverClient, userResolver) : resolverClient,
75
- }
76
-
77
- setResolver(resolved.resolver)
78
- setMacros([...defaultMacros, ...(options.macros ?? [])])
79
-
80
- // `sdk` swaps the per-operation functions for the class-based SDK; left unset, the standalone
81
- // functions (which query plugins consume) stay.
82
- addGenerator(resolved.sdk ? createSdkGenerator<PluginFetch>({ sse: sseTemplatePath }) : clientGenerator)
83
-
84
- const root = path.resolve(config.root, config.output.path)
85
-
86
- injectFile({
87
- baseName: 'serializers.ts',
88
- path: path.resolve(root, '.kubb/serializers.ts'),
89
- copy: serializersTemplatePath,
90
- })
91
-
92
- injectFile({
93
- baseName: 'core.ts',
94
- path: path.resolve(root, '.kubb/core.ts'),
95
- copy: coreTemplatePath,
96
- })
97
-
98
- injectFile({
99
- baseName: 'client.ts',
100
- path: path.resolve(root, '.kubb/client.ts'),
101
- copy: fetchClientTemplatePath,
102
- })
103
-
104
- injectFile({
105
- baseName: 'standardSchema.ts',
106
- path: path.resolve(root, '.kubb/standardSchema.ts'),
107
- copy: standardSchemaTemplatePath,
108
- })
109
-
41
+ const resolved = resolveClientOptions(options)
42
+ setupClient({ name: 'fetch', resolved, macros: options.macros, client: fileURLToPath(new URL('../templates/client.ts', import.meta.url)) })
110
43
  return resolved
111
44
  },
112
45
  })
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { PluginType } from '@kubb/kit'
2
- import type { Options, ResolvedOptions, ResolverClient } from '@internals/client'
2
+ import type { ClientOptions as Options, ClientResolvedOptions as ResolvedOptions, ResolverClient } from '@kubb/plugin-client'
3
3
 
4
- export type { Options, ResolvedOptions, ResolverClient } from '@internals/client'
4
+ export type { ClientOptions as Options, ClientResolvedOptions as ResolvedOptions, ResolverClient } from '@kubb/plugin-client'
5
5
 
6
6
  /**
7
7
  * The plugin factory type for `@kubb/plugin-fetch`. Shares the options surface, resolver, and