Skip to content

Commit 8882e41

Browse files
Use Stream Docs (#10420)
* use stream docs * fix typo --------- Co-authored-by: Joe Tannenbaum <joe.tannenbaum@laravel.com>
1 parent 5e6d0c2 commit 8882e41

File tree

1 file changed

+333
-14
lines changed

1 file changed

+333
-14
lines changed

‎responses.md

Lines changed: 333 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
- [JSON Responses](#json-responses)
1515
- [File Downloads](#file-downloads)
1616
- [File Responses](#file-responses)
17-
- [Streamed Responses](#streamed-responses)
17+
- [Streamed Responses](#streamed-responses)
18+
- [Consuming Streamed Responses](#consuming-streamed-responses)
19+
- [Streamed JSON Responses](#streamed-json-responses)
20+
- [Event Streams (SSE)](#event-streams)
21+
- [Streamed Downloads](#streamed-downloads)
1822
- [Response Macros](#response-macros)
1923

2024
<a name="creating-responses"></a>
@@ -360,20 +364,15 @@ return response()->file($pathToFile, $headers);
360364
```
361365

362366
<a name="streamed-responses"></a>
363-
### Streamed Responses
367+
## Streamed Responses
364368

365369
By streaming data to the client as it is generated, you can significantly reduce memory usage and improve performance, especially for very large responses. Streamed responses allow the client to begin processing data before the server has finished sending it:
366370

367371
```php
368-
function streamedContent(): Generator {
369-
yield 'Hello, ';
370-
yield 'World!';
371-
}
372-
373372
Route::get('/stream', function () {
374373
return response()->stream(function (): void {
375-
foreach (streamedContent() as $chunk) {
376-
echo $chunk;
374+
foreach (['developer', 'admin'] as $string) {
375+
echo $string;
377376
ob_flush();
378377
flush();
379378
sleep(2); // Simulate delay between chunks...
@@ -382,11 +381,262 @@ Route::get('/stream', function () {
382381
});
383382
```
384383

385-
> [!NOTE]
386-
> Internally, Laravel utilizes PHP's output buffering functionality. As you can see in the example above, you should use the `ob_flush` and `flush` functions to push buffered content to the client.
384+
For convenience, if the closure you provide to the `stream` method returns a [Generator](https://www.php.net/manual/en/language.generators.overview.php), Laravel will automatically flush the output buffer between strings returned by the generator, as well as disable Nginx output buffering:
385+
386+
```php
387+
Route::get('/chat', function () {
388+
return response()->stream(function (): void {
389+
$stream = OpenAI::client()->chat()->createStreamed(...);
390+
391+
foreach ($stream as $response) {
392+
yield $response->choices[0];
393+
}
394+
});
395+
});
396+
```
397+
398+
<a name="consuming-streamed-responses"></a>
399+
### Consuming Streamed Responses
400+
401+
Streamed responses may be consumed using Laravel's `stream` npm package, which provides a convenient API for interacting with Laravel response and event streams. To get started, install the `@laravel/stream-react` or `@laravel/stream-vue` package:
402+
403+
```shell tab=React
404+
npm install @laravel/stream-react
405+
```
406+
407+
```shell tab=Vue
408+
npm install @laravel/stream-vue
409+
```
410+
411+
Then, `useStream` may be used to consume the event stream. After providing your stream URL, the hook will automatically update the `data` with the concatenated response as content is returned from your Laravel application:
412+
413+
```tsx tab=React
414+
import { useStream } from "@laravel/stream-react";
415+
416+
function App() {
417+
const { data, isFetching, isStreaming, send } = useStream("chat");
418+
419+
const sendMessage = () => {
420+
send({
421+
message: `Current timestamp: ${Date.now()}`,
422+
});
423+
};
424+
425+
return (
426+
<div>
427+
<div>{data}</div>
428+
{isFetching && <div>Connecting...</div>}
429+
{isStreaming && <div>Generating...</div>}
430+
<button onClick={sendMessage}>Send Message</button>
431+
</div>
432+
);
433+
}
434+
```
435+
436+
```vue tab=Vue
437+
<script setup lang="ts">
438+
import { useStream } from "@laravel/stream-vue";
439+
440+
const { data, isFetching, isStreaming, send } = useStream("chat");
441+
442+
const sendMessage = () => {
443+
send({
444+
message: `Current timestamp: ${Date.now()}`,
445+
});
446+
};
447+
</script>
448+
449+
<template>
450+
<div>
451+
<div>{{ data }}</div>
452+
<div v-if="isFetching">Connecting...</div>
453+
<div v-if="isStreaming">Generating...</div>
454+
<button @click="sendMessage">Send Message</button>
455+
</div>
456+
</template>
457+
```
458+
459+
When sending data back to the stream via `send`, the active connection to the stream is canceled before sending the new data. All requests are sent as JSON `POST` requests.
460+
461+
The second argument given to `useStream` is an options object that you may use to customize the stream consumption behavior. The default values for this object are shown below:
462+
463+
```tsx tab=React
464+
import { useStream } from "@laravel/stream-react";
465+
466+
function App() {
467+
const { data } = useStream("chat", {
468+
id: undefined,
469+
initialInput: undefined,
470+
headers: undefined,
471+
csrfToken: undefined,
472+
onResponse: (response: Response) => void,
473+
onData: (data: string) => void,
474+
onCancel: () => void,
475+
onFinish: () => void,
476+
onError: (error: Error) => void,
477+
});
478+
479+
return <div>{data}</div>;
480+
}
481+
```
482+
483+
```vue tab=Vue
484+
<script setup lang="ts">
485+
import { useStream } from "@laravel/stream-vue";
486+
487+
const { data } = useStream("chat", {
488+
id: undefined,
489+
initialInput: undefined,
490+
headers: undefined,
491+
csrfToken: undefined,
492+
onResponse: (response: Response) => void,
493+
onData: (data: string) => void,
494+
onCancel: () => void,
495+
onFinish: () => void,
496+
onError: (error: Error) => void,
497+
});
498+
</script>
499+
500+
<template>
501+
<div>{{ data }}</div>
502+
</template>
503+
```
504+
505+
`onResponse` is triggered after a successful initial response from the stream and the raw [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) is passed to the callback. `onData` is called as each chunk is received - the current chunk is passed to the callback. `onFinish` is called when a stream has finished and when an error is thrown during the fetch / read cycle.
506+
507+
By default, a request is not made to the stream on initialization. You may pass an initial payload to the stream by using the `initialInput` option:
508+
509+
```tsx tab=React
510+
import { useStream } from "@laravel/stream-react";
511+
512+
function App() {
513+
const { data } = useStream("chat", {
514+
initialInput: {
515+
message: "Introduce yourself.",
516+
},
517+
});
518+
519+
return <div>{data}</div>;
520+
}
521+
```
522+
523+
```vue tab=Vue
524+
<script setup lang="ts">
525+
import { useStream } from "@laravel/stream-vue";
526+
527+
const { data } = useStream("chat", {
528+
initialInput: {
529+
message: "Introduce yourself.",
530+
},
531+
});
532+
</script>
533+
534+
<template>
535+
<div>{{ data }}</div>
536+
</template>
537+
```
538+
539+
To cancel a stream manually, you may use the `cancel` method returned from the hook:
540+
541+
```tsx tab=React
542+
import { useStream } from "@laravel/stream-react";
543+
544+
function App() {
545+
const { data, cancel } = useStream("chat");
546+
547+
return (
548+
<div>
549+
<div>{data}</div>
550+
<button onClick={cancel}>Cancel</button>
551+
</div>
552+
);
553+
}
554+
```
555+
556+
```vue tab=Vue
557+
<script setup lang="ts">
558+
import { useStream } from "@laravel/stream-vue";
559+
560+
const { data, cancel } = useStream("chat");
561+
</script>
562+
563+
<template>
564+
<div>
565+
<div>{{ data }}</div>
566+
<button @click="cancel">Cancel</button>
567+
</div>
568+
</template>
569+
```
570+
571+
Each time the `useStream` hook is used, a random `id` is generated to identify the stream. This is sent back to the server with each request in the `X-STREAM-ID` header. When consuming the same stream from multiple components, you can read and write to the stream by providing your own `id`:
572+
573+
```tsx tab=React
574+
// App.tsx
575+
import { useStream } from "@laravel/stream-react";
576+
577+
function App() {
578+
const { data, id } = useStream("chat");
579+
580+
return (
581+
<div>
582+
<div>{data}</div>
583+
<StreamStatus id={id} />
584+
</div>
585+
);
586+
}
587+
588+
// StreamStatus.tsx
589+
import { useStream } from "@laravel/stream-react";
590+
591+
function StreamStatus({ id }) {
592+
const { isFetching, isStreaming } = useStream("chat", { id });
593+
594+
return (
595+
<div>
596+
{isFetching && <div>Connecting...</div>}
597+
{isStreaming && <div>Generating...</div>}
598+
</div>
599+
);
600+
}
601+
```
602+
603+
```vue tab=Vue
604+
<!-- App.vue -->
605+
<script setup lang="ts">
606+
import { useStream } from "@laravel/stream-vue";
607+
import StreamStatus from "./StreamStatus.vue";
608+
609+
const { data, id } = useStream("chat");
610+
</script>
611+
612+
<template>
613+
<div>
614+
<div>{{ data }}</div>
615+
<StreamStatus :id="id" />
616+
</div>
617+
</template>
618+
619+
<!-- StreamStatus.vue -->
620+
<script setup lang="ts">
621+
import { useStream } from "@laravel/stream-vue";
622+
623+
const props = defineProps<{
624+
id: string;
625+
}>();
626+
627+
const { isFetching, isStreaming } = useStream("chat", { id: props.id });
628+
</script>
629+
630+
<template>
631+
<div>
632+
<div v-if="isFetching">Connecting...</div>
633+
<div v-if="isStreaming">Generating...</div>
634+
</div>
635+
</template>
636+
```
387637

388638
<a name="streamed-json-responses"></a>
389-
#### Streamed JSON Responses
639+
### Streamed JSON Responses
390640

391641
If you need to stream JSON data incrementally, you may utilize the `streamJson` method. This method is especially useful for large datasets that need to be sent progressively to the browser in a format that can be easily parsed by JavaScript:
392642

@@ -400,8 +650,74 @@ Route::get('/users.json', function () {
400650
});
401651
```
402652

653+
The `useJsonStream` hook is identical to the [`useStream` hook](#consuming-streamed-responses) except that it will attempt to parse the data as JSON once it has finished streaming:
654+
655+
```tsx tab=React
656+
import { useJsonStream } from "@laravel/stream-react";
657+
658+
type User = {
659+
id: number;
660+
name: string;
661+
email: string;
662+
};
663+
664+
function App() {
665+
const { data, send } = useJsonStream<{ users: User[] }>("users");
666+
667+
const loadUsers = () => {
668+
send({
669+
query: "taylor",
670+
});
671+
};
672+
673+
return (
674+
<div>
675+
<ul>
676+
{data?.users.map((user) => (
677+
<li>
678+
{user.id}: {user.name}
679+
</li>
680+
))}
681+
</ul>
682+
<button onClick={loadUsers}>Load Users</button>
683+
</div>
684+
);
685+
}
686+
```
687+
688+
```vue tab=Vue
689+
<script setup lang="ts">
690+
import { useJsonStream } from "@laravel/stream-vue";
691+
692+
type User = {
693+
id: number;
694+
name: string;
695+
email: string;
696+
};
697+
698+
const { data, send } = useJsonStream<{ users: User[] }>("users");
699+
700+
const loadUsers = () => {
701+
send({
702+
query: "taylor",
703+
});
704+
};
705+
</script>
706+
707+
<template>
708+
<div>
709+
<ul>
710+
<li v-for="user in data?.users" :key="user.id">
711+
{{ user.id }}: {{ user.name }}
712+
</li>
713+
</ul>
714+
<button @click="loadUsers">Load Users</button>
715+
</div>
716+
</template>
717+
```
718+
403719
<a name="event-streams"></a>
404-
#### Event Streams
720+
### Event Streams (SSE)
405721

406722
The `eventStream` method may be used to return a server-sent events (SSE) streamed response using the `text/event-stream` content type. The `eventStream` method accepts a closure which should [yield](https://www.php.net/manual/en/language.generators.overview.php) responses to the stream as the responses become available:
407723

@@ -428,6 +744,9 @@ yield new StreamedEvent(
428744
);
429745
```
430746

747+
<a name="consuming-event-streams"></a>
748+
#### Consuming Event Streams
749+
431750
Event streams may be consumed using Laravel's `stream` npm package, which provides a convenient API for interacting with Laravel event streams. To get started, install the `@laravel/stream-react` or `@laravel/stream-vue` package:
432751

433752
```shell tab=React
@@ -533,7 +852,7 @@ return response()->eventStream(function () {
533852
```
534853

535854
<a name="streamed-downloads"></a>
536-
#### Streamed Downloads
855+
### Streamed Downloads
537856

538857
Sometimes you may wish to turn the string response of a given operation into a downloadable response without having to write the contents of the operation to disk. You may use the `streamDownload` method in this scenario. This method accepts a callback, filename, and an optional array of headers as its arguments:
539858

0 commit comments

Comments
 (0)