这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/brown-pandas-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': minor
---

feat(client): add support for server-sent events (SSE)
22 changes: 22 additions & 0 deletions docs/openapi-ts/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@ description: Migrating to @hey-api/openapi-ts.

While we try to avoid breaking changes, sometimes it's unavoidable in order to offer you the latest features. This page lists changes that require updates to your code. If you run into a problem with migration, please [open an issue](https://github.com/hey-api/openapi-ts/issues).

## v0.81.0

### Server-Sent Events (SSE)

This release adds support for server-sent events (SSE). Instead of treating `text/event-stream` content types as regular HTTP methods, we now generate SSE streams. In practice, you will want to update your affected endpoints to process streamed events.

::: code-group

```js [before]
const { data } = await foo();
console.log(data.type);
```

```js [after]
const { stream } = await foo();
for await (const event of stream) {
console.log(event.type);
}
```

:::

## v0.80.0

### Added Zod 4 and Zod Mini
Expand Down
75 changes: 75 additions & 0 deletions packages/openapi-ts-tests/main/test/3.1.x.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,81 @@ describe(`OpenAPI ${version}`, () => {
description:
'generates validator schemas for all integer format combinations (number/integer/string types with int8, int16, int32, int64, uint8, uint16, uint32, uint64 formats)',
},
{
config: createConfig({
input: 'opencode.yaml',
output: 'sse-angular',
parser: {
filters: {
operations: {
include: ['GET /event'],
},
},
},
plugins: ['@hey-api/client-angular', '@hey-api/sdk'],
}),
description: 'client with SSE (Angular)',
},
{
config: createConfig({
input: 'opencode.yaml',
output: 'sse-axios',
parser: {
filters: {
operations: {
include: ['GET /event'],
},
},
},
plugins: ['@hey-api/client-axios', '@hey-api/sdk'],
}),
description: 'client with SSE (Axios)',
},
{
config: createConfig({
input: 'opencode.yaml',
output: 'sse-fetch',
parser: {
filters: {
operations: {
include: ['GET /event'],
},
},
},
plugins: ['@hey-api/client-fetch', '@hey-api/sdk'],
}),
description: 'client with SSE (Fetch)',
},
{
config: createConfig({
input: 'opencode.yaml',
output: 'sse-next',
parser: {
filters: {
operations: {
include: ['GET /event'],
},
},
},
plugins: ['@hey-api/client-next', '@hey-api/sdk'],
}),
description: 'client with SSE (Next.js)',
},
{
config: createConfig({
input: 'opencode.yaml',
output: 'sse-nuxt',
parser: {
filters: {
operations: {
include: ['GET /event'],
},
},
},
plugins: ['@hey-api/client-nuxt', '@hey-api/sdk'],
}),
description: 'client with SSE (Nuxt)',
},
];

it.each(scenarios)('$description', async ({ config }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// This file is auto-generated by @hey-api/openapi-ts

import type { Client, Config, ResolvedRequestOptions } from './types.gen';
import { createSseClient } from '../core/serverSentEvents.gen';
import type {
Client,
Config,
RequestOptions,
ResolvedRequestOptions,
} from './types.gen';
import {
buildUrl,
createConfig,
Expand Down Expand Up @@ -33,7 +39,7 @@ export const createClient = (config: Config = {}): Client => {
ResolvedRequestOptions
>();

const request: Client['request'] = async (options) => {
const beforeRequest = async (options: RequestOptions) => {
const opts = {
..._config,
...options,
Expand Down Expand Up @@ -63,6 +69,13 @@ export const createClient = (config: Config = {}): Client => {
}

const url = buildUrl(opts);

return { opts, url };
};

const request: Client['request'] = async (options) => {
// @ts-expect-error
const { opts, url } = await beforeRequest(options);
const requestInit: ReqInit = {
redirect: 'follow',
...opts,
Expand Down Expand Up @@ -180,20 +193,35 @@ export const createClient = (config: Config = {}): Client => {
};
};

const makeMethod = (method: Required<Config>['method']) => {
const fn = (options: RequestOptions) => request({ ...options, method });
fn.sse = async (options: RequestOptions) => {
const { opts, url } = await beforeRequest(options);
return createSseClient({
...opts,
body: opts.body as BodyInit | null | undefined,
headers: opts.headers as unknown as Record<string, string>,
method,
url,
});
};
return fn;
};

return {
buildUrl,
connect: (options) => request({ ...options, method: 'CONNECT' }),
delete: (options) => request({ ...options, method: 'DELETE' }),
get: (options) => request({ ...options, method: 'GET' }),
connect: makeMethod('CONNECT'),
delete: makeMethod('DELETE'),
get: makeMethod('GET'),
getConfig,
head: (options) => request({ ...options, method: 'HEAD' }),
head: makeMethod('HEAD'),
interceptors,
options: (options) => request({ ...options, method: 'OPTIONS' }),
patch: (options) => request({ ...options, method: 'PATCH' }),
post: (options) => request({ ...options, method: 'POST' }),
put: (options) => request({ ...options, method: 'PUT' }),
options: makeMethod('OPTIONS'),
patch: makeMethod('PATCH'),
post: makeMethod('POST'),
put: makeMethod('PUT'),
request,
setConfig,
trace: (options) => request({ ...options, method: 'TRACE' }),
};
trace: makeMethod('TRACE'),
} as Client;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// This file is auto-generated by @hey-api/openapi-ts

import type { Auth } from '../core/auth.gen';
import type {
ServerSentEventsOptions,
ServerSentEventsResult,
} from '../core/serverSentEvents.gen';
import type {
Client as CoreClient,
Config as CoreConfig,
Expand Down Expand Up @@ -61,13 +65,22 @@ export interface Config<T extends ClientOptions = ClientOptions>
}

export interface RequestOptions<
TData = unknown,
TResponseStyle extends ResponseStyle = 'fields',
ThrowOnError extends boolean = boolean,
Url extends string = string,
> extends Config<{
responseStyle: TResponseStyle;
throwOnError: ThrowOnError;
}> {
responseStyle: TResponseStyle;
throwOnError: ThrowOnError;
}>,
Pick<
ServerSentEventsOptions<TData>,
| 'onSseError'
| 'onSseEvent'
| 'sseDefaultRetryDelay'
| 'sseMaxRetryAttempts'
| 'sseMaxRetryDelay'
> {
/**
* Any body that you want to add to your request.
*
Expand All @@ -87,7 +100,7 @@ export interface ResolvedRequestOptions<
TResponseStyle extends ResponseStyle = 'fields',
ThrowOnError extends boolean = boolean,
Url extends string = string,
> extends RequestOptions<TResponseStyle, ThrowOnError, Url> {
> extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
serializedBody?: string;
}

Expand Down Expand Up @@ -142,23 +155,39 @@ export interface ClientOptions {
throwOnError?: boolean;
}

type MethodFn = <
type MethodFnBase = <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>,
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;

type MethodFnServerSentEvents = <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
) => Promise<ServerSentEventsResult<TData, TError>>;

type MethodFn = MethodFnBase & {
sse: MethodFnServerSentEvents;
};

type RequestFn = <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> &
Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>,
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> &
Pick<
Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>,
'method'
>,
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;

type BuildUrlFn = <
Expand Down Expand Up @@ -201,9 +230,10 @@ type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
export type Options<
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
TResponse = unknown,
TResponseStyle extends ResponseStyle = 'fields',
> = OmitKeys<
RequestOptions<TResponseStyle, ThrowOnError>,
RequestOptions<TResponse, TResponseStyle, ThrowOnError>,
'body' | 'path' | 'query' | 'url'
> &
Omit<TData, 'url'>;
Expand All @@ -215,18 +245,22 @@ export type OptionsLegacyParser<
> = TData extends { body?: any }
? TData extends { headers?: any }
? OmitKeys<
RequestOptions<TResponseStyle, ThrowOnError>,
RequestOptions<unknown, TResponseStyle, ThrowOnError>,
'body' | 'headers' | 'url'
> &
TData
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> &
: OmitKeys<
RequestOptions<unknown, TResponseStyle, ThrowOnError>,
'body' | 'url'
> &
TData &
Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'>
Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'headers'>
: TData extends { headers?: any }
? OmitKeys<
RequestOptions<TResponseStyle, ThrowOnError>,
RequestOptions<unknown, TResponseStyle, ThrowOnError>,
'headers' | 'url'
> &
TData &
Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'>
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'body'>
: OmitKeys<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'url'> &
TData;
Loading
Loading