UNPKG

@laravel/stream-vue

Version:
312 lines (241 loc) 8.4 kB
# Laravel Stream for Vue <p align="left"> <a href="https://github.com/laravel/stream/actions/workflows/tests.yml"><img src="https://github.com/laravel/stream/actions/workflows/tests.yml/badge.svg" alt="Build Status"></a> <a href="https://www.npmjs.com/package/@laravel/stream-vue"><img src="https://img.shields.io/npm/dt/@laravel/stream-vue" alt="Total Downloads"></a> <a href="https://www.npmjs.com/package/@laravel/stream-vue"><img src="https://img.shields.io/npm/v/@laravel/stream-vue" alt="Latest Stable Version"></a> <a href="https://www.npmjs.com/package/@laravel/stream-vue"><img src="https://img.shields.io/npm/l/@laravel/stream-vue" alt="License"></a> </p> Easily consume streams in your Vue application. ## Installation ```bash npm install @laravel/stream-vue ``` ## Streaming Responses > [!IMPORTANT] > The `useStream` hook is currently in Beta, the API is subject to change prior to the v1.0.0 release. All notable changes will be documented in the [changelog](./../../CHANGELOG.md). The `useStream` hook allows you to seamlessly consume [streamed responses](https://laravel.com/docs/responses#streamed-responses) in your Vue application. Provide your stream URL and the hook will automatically update `data` with the concatenated response as data is returned from your server: ```vue <script setup lang="ts"> import { useStream } from "@laravel/stream-vue"; const { data, isFetching, isStreaming, send } = useStream("chat"); const sendMessage = () => { send({ message: `Current timestamp: ${Date.now()}`, }); }; </script> <template> <div> <div>{{ data }}</div> <div v-if="isFetching">Connecting...</div> <div v-if="isStreaming">Generating...</div> <button @click="sendMessage">Send Message</button> </div> </template> ``` When sending data back to the stream, the active connection to the stream is canceled before sending the new data. All requests are sent as JSON `POST` requests. The second argument given to `useStream` is an options object that you may use to customize the stream consumption behavior: ```ts type StreamOptions = { id?: string; initialInput?: Record<string, any>; headers?: Record<string, string>; csrfToken?: string; json?: boolean; credentials?: RequestCredentials; onResponse?: (response: Response) => void; onData?: (data: string) => void; onCancel?: () => void; onFinish?: () => void; onError?: (error: Error) => void; onBeforeSend?: (request: RequestInit) => boolean | RequestInit | void; }; ``` `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. `onBeforeSend` is called right before sending the request to the server and receives the `RequestInit` object as an argument. Returning `false` from this callback cancels the request, returning a [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object will override the existing `RequestInit` object. By default, a request is not made the to stream on initialization. You may pass an initial payload to the stream by using the `initialInput` option: ```vue <script setup lang="ts"> import { useStream } from "@laravel/stream-vue"; const { data } = useStream("chat", { initialInput: { message: "Introduce yourself.", }, }); </script> <template> <div>{{ data }}</div> </template> ``` To cancel a stream manually, you may use the `cancel` method returned from the hook: ```vue <script setup lang="ts"> import { useStream } from "@laravel/stream-vue"; const { data, cancel } = useStream("chat"); </script> <template> <div> <div>{{ data }}</div> <button @click="cancel">Cancel</button> </div> </template> ``` 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`: ```vue <!-- App.vue --> <script setup lang="ts"> import { useStream } from "@laravel/stream-vue"; import StreamStatus from "./StreamStatus.vue"; const { data, id } = useStream("chat"); </script> <template> <div> <div>{{ data }}</div> <StreamStatus :id="id" /> </div> </template> ``` ```vue <!-- StreamStatus.vue --> <script setup lang="ts"> import { useStream } from "@laravel/stream-vue"; const props = defineProps<{ id: string; }>(); const { isFetching, isStreaming } = useStream("chat", { id: props.id }); </script> <template> <div> <div v-if="isFetching">Connecting...</div> <div v-if="isStreaming">Generating...</div> </div> </template> ``` The `useJsonStream` hook is identical to the `useStream` hook except that it will attempt to parse the data as JSON once it has finished streaming: ```vue <script setup lang="ts"> import { useJsonStream } from "@laravel/stream-vue"; type User = { id: number; name: string; email: string; }; const { data, send } = useJsonStream<{ users: User[] }>("users"); const loadUsers = () => { send({ query: "taylor", }); }; </script> <template> <div> <ul> <li v-for="user in data?.users" :key="user.id"> {{ user.id }}: {{ user.name }} </li> </ul> <button @click="loadUsers">Load Users</button> </div> </template> ``` ## Event Streams (SSE) The `useEventStream` hook allows you to seamlessly consume [Server-Sent Events (SSE)](https://laravel.com/docs/responses#event-streams) in your Vue application. Provide your stream URL and the hook will automatically update the `message` with the concatenated response as messages are returned from your server: ```vue <script setup lang="ts"> import { useEventStream } from "@laravel/stream-vue"; const { message } = useEventStream("/stream"); </script> <template> <div>{{ message }}</div> </template> ``` You also have access to the array of message parts: ```vue <script setup lang="ts"> import { useEventStream } from "@laravel/stream-vue"; const { messageParts } = useEventStream("/stream"); </script> <template> <ul> <li v-for="message in messageParts"> {{ message }} </li> </ul> </template> ``` If you'd like to listen to multiple events: ```vue <script setup lang="ts"> import { useEventStream } from "@laravel/stream-vue"; useEventStream("/stream", { eventName: ["update", "create"], onMessage: (event) => { if (event.type === "update") { // Handle update } else { // Handle create } }, }); </script> ``` The second parameter is an options object where all properties are optional (defaults are shown below): ```vue <script setup lang="ts"> import { useEventStream } from "@laravel/stream-vue"; const { message } = useEventStream("/stream", { event: "update", onMessage: (message) => { // }, onError: (error) => { // }, onComplete: () => { // }, endSignal: "</stream>", glue: " ", replace: false, }); </script> ``` You can close the connection manually by using the returned `close` function: ```vue <script setup lang="ts"> import { useEventStream } from "@laravel/stream-vue"; import { onMounted } from "vue"; const { message, close } = useEventStream("/stream"); onMounted(() => { setTimeout(() => { close(); }, 3000); }); </script> <template> <div>{{ message }}</div> </template> ``` The `clearMessage` function may be used to clear the message content that has been received so far: ```vue <script setup lang="ts"> import { useEventStream } from "@laravel/stream-vue"; import { onMounted } from "vue"; const { message, clearMessage } = useEventStream("/stream"); onMounted(() => { setTimeout(() => { clearMessage(); }, 3000); }); </script> <template> <div>{{ message }}</div> </template> ``` ## License Laravel Stream is open-sourced software licensed under the [MIT license](LICENSE.md).