Skip to content

Commit 3ae263d

Browse files
authored
Merge pull request #2 from malcolm-kee/claude/validate-sse-example-FIuJh
docs: fix SSE examples to use actual client-specific features
2 parents 3e9e18f + 1bb6dfa commit 3ae263d

File tree

4 files changed

+130
-78
lines changed

4 files changed

+130
-78
lines changed

docs/openapi-ts/clients/angular.md

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -210,18 +210,61 @@ When your OpenAPI spec defines endpoints with `text/event-stream` responses, the
210210

211211
### Consuming a stream
212212

213-
Create a service to manage the SSE connection and expose state via signals.
213+
With the recommended Angular configuration, the SDK generates `@Injectable` service classes. SSE methods are instance methods on these services.
214214

215215
```ts
216-
import { Injectable, signal } from '@angular/core';
217-
import { watchStockPrices } from './client/sdk.gen';
218-
import type { StockUpdate } from './client/types.gen';
216+
// openapi-ts.config.ts
217+
export default defineConfig({
218+
plugins: [
219+
'@hey-api/client-angular',
220+
{
221+
name: '@hey-api/sdk',
222+
operations: {
223+
containerName: '{{name}}Service',
224+
strategy: 'byTags',
225+
},
226+
},
227+
],
228+
});
229+
```
219230

231+
The generated service has SSE methods alongside regular methods:
232+
233+
```ts
234+
// Generated: sdk.gen.ts
220235
@Injectable({ providedIn: 'root' })
221236
export class StockService {
222-
readonly updates = signal<StockUpdate[]>([]);
223-
readonly status = signal<'connected' | 'disconnected' | 'error'>('disconnected');
237+
public watchStockPrices<ThrowOnError extends boolean = false>(options?) {
238+
return (options?.client ?? client).sse.get<...>({ url: '/stock/watch', ...options });
239+
}
240+
}
241+
```
242+
243+
Inject the service in your component. Use signals for state and `ngOnDestroy` for cleanup.
244+
245+
```ts
246+
import { Component, inject, OnDestroy, signal } from '@angular/core';
247+
import { StockService } from './client/sdk.gen';
248+
import type { StockUpdate } from './client/types.gen';
249+
250+
@Component({
251+
selector: 'app-stock-ticker',
252+
template: `
253+
<button (click)="connect()">Connect</button>
254+
<button (click)="disconnect()">Disconnect</button>
255+
<p>Status: {{ status() }}</p>
256+
<ul>
257+
@for (update of updates(); track $index) {
258+
<li>{{ update | json }}</li>
259+
}
260+
</ul>
261+
`,
262+
})
263+
export class StockTickerComponent implements OnDestroy {
264+
#stockService = inject(StockService);
224265

266+
updates = signal<StockUpdate[]>([]);
267+
status = signal<'connected' | 'disconnected' | 'error'>('disconnected');
225268
#controller: AbortController | null = null;
226269

227270
async connect() {
@@ -230,7 +273,7 @@ export class StockService {
230273
this.updates.set([]);
231274

232275
try {
233-
const { stream } = await watchStockPrices({
276+
const { stream } = await this.#stockService.watchStockPrices({
234277
signal: this.#controller.signal,
235278
});
236279

@@ -251,41 +294,17 @@ export class StockService {
251294
this.#controller = null;
252295
this.status.set('disconnected');
253296
}
254-
}
255-
```
256-
257-
Then inject the service in your component and clean up on destroy.
258-
259-
```ts
260-
import { Component, inject, OnDestroy } from '@angular/core';
261-
import { StockService } from './stock.service';
262-
263-
@Component({
264-
selector: 'app-stock-ticker',
265-
template: `
266-
<button (click)="connect()">Connect</button>
267-
<button (click)="stockService.disconnect()">Disconnect</button>
268-
<p>Status: {{ stockService.status() }}</p>
269-
<ul>
270-
@for (update of stockService.updates(); track $index) {
271-
<li>{{ update | json }}</li>
272-
}
273-
</ul>
274-
`,
275-
})
276-
export class StockTickerComponent implements OnDestroy {
277-
stockService = inject(StockService);
278-
279-
connect() {
280-
this.stockService.connect();
281-
}
282297

283298
ngOnDestroy() {
284-
this.stockService.disconnect();
299+
this.disconnect();
285300
}
286301
}
287302
```
288303

304+
::: tip
305+
Ensure `provideHeyApiClient(client)` is registered in your `app.config.ts` providers, alongside `provideHttpClient(withFetch())`.
306+
:::
307+
289308
### Callbacks
290309

291310
You can use `onSseEvent` and `onSseError` callbacks for additional event and error processing.

docs/openapi-ts/clients/angular/v19.md

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -210,18 +210,61 @@ When your OpenAPI spec defines endpoints with `text/event-stream` responses, the
210210

211211
### Consuming a stream
212212

213-
Create a service to manage the SSE connection and expose state via signals.
213+
With the recommended Angular configuration, the SDK generates `@Injectable` service classes. SSE methods are instance methods on these services.
214214

215215
```ts
216-
import { Injectable, signal } from '@angular/core';
217-
import { watchStockPrices } from './client/sdk.gen';
218-
import type { StockUpdate } from './client/types.gen';
216+
// openapi-ts.config.ts
217+
export default defineConfig({
218+
plugins: [
219+
'@hey-api/client-angular',
220+
{
221+
name: '@hey-api/sdk',
222+
operations: {
223+
containerName: '{{name}}Service',
224+
strategy: 'byTags',
225+
},
226+
},
227+
],
228+
});
229+
```
219230

231+
The generated service has SSE methods alongside regular methods:
232+
233+
```ts
234+
// Generated: sdk.gen.ts
220235
@Injectable({ providedIn: 'root' })
221236
export class StockService {
222-
readonly updates = signal<StockUpdate[]>([]);
223-
readonly status = signal<'connected' | 'disconnected' | 'error'>('disconnected');
237+
public watchStockPrices<ThrowOnError extends boolean = false>(options?) {
238+
return (options?.client ?? client).sse.get<...>({ url: '/stock/watch', ...options });
239+
}
240+
}
241+
```
242+
243+
Inject the service in your component. Use signals for state and `ngOnDestroy` for cleanup.
244+
245+
```ts
246+
import { Component, inject, OnDestroy, signal } from '@angular/core';
247+
import { StockService } from './client/sdk.gen';
248+
import type { StockUpdate } from './client/types.gen';
249+
250+
@Component({
251+
selector: 'app-stock-ticker',
252+
template: `
253+
<button (click)="connect()">Connect</button>
254+
<button (click)="disconnect()">Disconnect</button>
255+
<p>Status: {{ status() }}</p>
256+
<ul>
257+
@for (update of updates(); track $index) {
258+
<li>{{ update | json }}</li>
259+
}
260+
</ul>
261+
`,
262+
})
263+
export class StockTickerComponent implements OnDestroy {
264+
#stockService = inject(StockService);
224265

266+
updates = signal<StockUpdate[]>([]);
267+
status = signal<'connected' | 'disconnected' | 'error'>('disconnected');
225268
#controller: AbortController | null = null;
226269

227270
async connect() {
@@ -230,7 +273,7 @@ export class StockService {
230273
this.updates.set([]);
231274

232275
try {
233-
const { stream } = await watchStockPrices({
276+
const { stream } = await this.#stockService.watchStockPrices({
234277
signal: this.#controller.signal,
235278
});
236279

@@ -251,41 +294,17 @@ export class StockService {
251294
this.#controller = null;
252295
this.status.set('disconnected');
253296
}
254-
}
255-
```
256-
257-
Then inject the service in your component and clean up on destroy.
258-
259-
```ts
260-
import { Component, inject, OnDestroy } from '@angular/core';
261-
import { StockService } from './stock.service';
262-
263-
@Component({
264-
selector: 'app-stock-ticker',
265-
template: `
266-
<button (click)="connect()">Connect</button>
267-
<button (click)="stockService.disconnect()">Disconnect</button>
268-
<p>Status: {{ stockService.status() }}</p>
269-
<ul>
270-
@for (update of stockService.updates(); track $index) {
271-
<li>{{ update | json }}</li>
272-
}
273-
</ul>
274-
`,
275-
})
276-
export class StockTickerComponent implements OnDestroy {
277-
stockService = inject(StockService);
278-
279-
connect() {
280-
this.stockService.connect();
281-
}
282297

283298
ngOnDestroy() {
284-
this.stockService.disconnect();
299+
this.disconnect();
285300
}
286301
}
287302
```
288303

304+
::: tip
305+
Ensure `provideHeyApiClient(client)` is registered in your `app.config.ts` providers, alongside `provideHttpClient(withFetch())`.
306+
:::
307+
289308
### Callbacks
290309

291310
You can use `onSseEvent` and `onSseError` callbacks for additional event and error processing.

docs/openapi-ts/clients/next-js.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@ When your OpenAPI spec defines endpoints with `text/event-stream` responses, the
251251

252252
### Consuming a stream
253253

254-
SSE requires a client component since it uses browser APIs and React hooks.
254+
::: tip
255+
Unlike regular SDK calls which support Next.js caching options (`cache`, `next: { revalidate, tags }`), SSE streams are long-lived connections and must run in client components.
256+
:::
255257

256258
```tsx
257259
'use client';

docs/openapi-ts/clients/nuxt.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,19 +222,30 @@ When your OpenAPI spec defines endpoints with `text/event-stream` responses, the
222222

223223
### Consuming a stream
224224

225+
::: tip
226+
SSE endpoints always return `{ stream }` with an `AsyncGenerator`. The `composable` option (`useAsyncData`, `useFetch`, etc.) does not apply to SSE — it is designed for request-response patterns with caching. SSE streams are consumed client-side only.
227+
:::
228+
229+
With the `@hey-api/nuxt` module, SDK functions are auto-imported. Vue refs passed as parameters are automatically unwrapped.
230+
225231
```vue
226232
<script setup lang="ts">
227233
import { ref, onUnmounted } from 'vue';
228-
import { watchStockPrices } from './client/sdk.gen';
229-
import type { StockUpdate } from './client/types.gen';
234+
// With @hey-api/nuxt, these imports are auto-generated:
235+
import { watchSingleStock } from '#hey-api/sdk.gen';
236+
import type { StockUpdate } from '#hey-api/types.gen';
230237
231238
const updates = ref<StockUpdate[]>([]);
232-
const controller = new AbortController();
239+
const symbol = ref('AAPL');
240+
let controller: AbortController | null = null;
233241
234-
onUnmounted(() => controller.abort());
242+
onUnmounted(() => controller?.abort());
235243
236244
async function connect() {
237-
const { stream } = await watchStockPrices({
245+
controller = new AbortController();
246+
247+
const { stream } = await watchSingleStock({
248+
path: { symbol }, // Vue refs are unwrapped automatically
238249
signal: controller.signal,
239250
});
240251
@@ -245,6 +256,7 @@ async function connect() {
245256
</script>
246257
247258
<template>
259+
<input v-model="symbol" />
248260
<button @click="connect">Connect</button>
249261
<ul>
250262
<li v-for="(update, i) in updates" :key="i">

0 commit comments

Comments
 (0)