@laravel/stream-svelte 0.3.10

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.
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # Laravel Stream for Svelte
2
+
3
+ <p align="left">
4
+ <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>
5
+ <a href="https://www.npmjs.com/package/@laravel/stream-svelte"><img src="https://img.shields.io/npm/dt/@laravel/stream-svelte" alt="Total Downloads"></a>
6
+ <a href="https://www.npmjs.com/package/@laravel/stream-svelte"><img src="https://img.shields.io/npm/v/@laravel/stream-svelte" alt="Latest Stable Version"></a>
7
+ <a href="https://www.npmjs.com/package/@laravel/stream-svelte"><img src="https://img.shields.io/npm/l/@laravel/stream-svelte" alt="License"></a>
8
+ </p>
9
+
10
+ Easily consume streams in your Svelte application.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @laravel/stream-svelte
16
+ ```
17
+
18
+ ## Streaming Responses
19
+
20
+ > [!IMPORTANT]
21
+ > The `useStream` API 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).
22
+
23
+ The `useStream` function allows you to seamlessly consume [streamed responses](https://laravel.com/docs/responses#streamed-responses) in your Svelte application.
24
+
25
+ Call `useStream` at the top level of your component script (or in a `.svelte.ts` module). Provide your stream URL and the returned object will automatically update `data` with the concatenated response as data is returned from your server. The returned object is store-like: use `$stream` in the template to react to `data`, `isFetching`, and `isStreaming`.
26
+
27
+ ```svelte
28
+ <script>
29
+ import { useStream } from "@laravel/stream-svelte";
30
+
31
+ const stream = useStream("chat");
32
+
33
+ const sendMessage = () => {
34
+ stream.send({
35
+ message: `Current timestamp: ${Date.now()}`,
36
+ });
37
+ };
38
+ </script>
39
+
40
+ <div>
41
+ <div>{$stream.data}</div>
42
+ {#if $stream.isFetching}
43
+ <div>Connecting...</div>
44
+ {/if}
45
+ {#if $stream.isStreaming}
46
+ <div>Generating...</div>
47
+ {/if}
48
+ <button onclick={sendMessage}>Send Message</button>
49
+ </div>
50
+ ```
51
+
52
+ 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.
53
+
54
+ The second argument given to `useStream` is an options object that you may use to customize the stream consumption behavior:
55
+
56
+ ```ts
57
+ type StreamOptions = {
58
+ id?: string;
59
+ initialInput?: Record<string, any>;
60
+ headers?: Record<string, string>;
61
+ csrfToken?: string;
62
+ json?: boolean;
63
+ credentials?: RequestCredentials;
64
+ onResponse?: (response: Response) => void;
65
+ onData?: (data: string) => void;
66
+ onCancel?: () => void;
67
+ onFinish?: () => void;
68
+ onError?: (error: Error) => void;
69
+ onBeforeSend?: (request: RequestInit) => boolean | RequestInit | void;
70
+ };
71
+ ```
72
+
73
+ `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.
74
+
75
+ `onData` is called as each chunk is received, the current chunk is passed to the callback.
76
+
77
+ `onFinish` is called when a stream has finished and when an error is thrown during the fetch/read cycle.
78
+
79
+ `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.
80
+
81
+ 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:
82
+
83
+ ```svelte
84
+ <script>
85
+ import { useStream } from "@laravel/stream-svelte";
86
+
87
+ const stream = useStream("chat", {
88
+ initialInput: {
89
+ message: "Introduce yourself.",
90
+ },
91
+ });
92
+ </script>
93
+
94
+ <div>{$stream.data}</div>
95
+ ```
96
+
97
+ To cancel a stream manually, you may use the `cancel` method returned from the stream object:
98
+
99
+ ```svelte
100
+ <script>
101
+ import { useStream } from "@laravel/stream-svelte";
102
+
103
+ const stream = useStream("chat");
104
+ </script>
105
+
106
+ <div>
107
+ <div>{$stream.data}</div>
108
+ <button onclick={() => stream.cancel()}>Cancel</button>
109
+ </div>
110
+ ```
111
+
112
+ Each time `useStream` 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.
113
+
114
+ When consuming the same stream from multiple components, you can read and write to the stream by providing your own `id`:
115
+
116
+ ```svelte
117
+ <!-- App.svelte -->
118
+ <script>
119
+ import { useStream } from "@laravel/stream-svelte";
120
+ import StreamStatus from "./StreamStatus.svelte";
121
+
122
+ const stream = useStream("chat");
123
+ </script>
124
+
125
+ <div>
126
+ <div>{$stream.data}</div>
127
+ <StreamStatus id={stream.id} />
128
+ </div>
129
+ ```
130
+
131
+ ```svelte
132
+ <!-- StreamStatus.svelte -->
133
+ <script>
134
+ import { useStream } from "@laravel/stream-svelte";
135
+
136
+ let { id } = $props();
137
+
138
+ const stream = useStream("chat", { id });
139
+ </script>
140
+
141
+ <div>
142
+ {#if $stream.isFetching}
143
+ <div>Connecting...</div>
144
+ {/if}
145
+ {#if $stream.isStreaming}
146
+ <div>Generating...</div>
147
+ {/if}
148
+ </div>
149
+ ```
150
+
151
+ The `useJsonStream` function is identical to `useStream` except that it will attempt to parse the data as JSON once it has finished streaming:
152
+
153
+ ```svelte
154
+ <script>
155
+ import { useJsonStream } from "@laravel/stream-svelte";
156
+
157
+ type User = {
158
+ id: number;
159
+ name: string;
160
+ email: string;
161
+ };
162
+
163
+ const stream = useJsonStream<{ users: User[] }>("users");
164
+
165
+ const loadUsers = () => {
166
+ stream.send({
167
+ query: "taylor",
168
+ });
169
+ };
170
+ </script>
171
+
172
+ <div>
173
+ <ul>
174
+ {#if $stream.data?.users}
175
+ {#each $stream.data.users as user (user.id)}
176
+ <li>{user.id}: {user.name}</li>
177
+ {/each}
178
+ {/if}
179
+ </ul>
180
+ <button onclick={loadUsers}>Load Users</button>
181
+ </div>
182
+ ```
183
+
184
+ ## Event Streams (SSE)
185
+
186
+ The `useEventStream` function allows you to seamlessly consume [Server-Sent Events (SSE)](https://laravel.com/docs/responses#event-streams) in your Svelte application.
187
+
188
+ Provide your stream URL and the returned object will automatically update `message` with the concatenated response as messages are returned from your server:
189
+
190
+ ```svelte
191
+ <script>
192
+ import { useEventStream } from "@laravel/stream-svelte";
193
+
194
+ const eventStream = useEventStream("/stream");
195
+ </script>
196
+
197
+ <div>{$eventStream.message}</div>
198
+ ```
199
+
200
+ You also have access to the array of message parts:
201
+
202
+ ```svelte
203
+ <script>
204
+ import { useEventStream } from "@laravel/stream-svelte";
205
+
206
+ const eventStream = useEventStream("/stream");
207
+ </script>
208
+
209
+ <ul>
210
+ {#each $eventStream.messageParts as message}
211
+ <li>{message}</li>
212
+ {/each}
213
+ </ul>
214
+ ```
215
+
216
+ If you'd like to listen to multiple events:
217
+
218
+ ```svelte
219
+ <script>
220
+ import { useEventStream } from "@laravel/stream-svelte";
221
+
222
+ useEventStream("/stream", {
223
+ eventName: ["update", "create"],
224
+ onMessage: (event) => {
225
+ if (event.type === "update") {
226
+ // Handle update
227
+ } else {
228
+ // Handle create
229
+ }
230
+ },
231
+ });
232
+ </script>
233
+ ```
234
+
235
+ The second parameter is an options object where all properties are optional (defaults are shown below):
236
+
237
+ ```svelte
238
+ <script>
239
+ import { useEventStream } from "@laravel/stream-svelte";
240
+
241
+ const eventStream = useEventStream("/stream", {
242
+ eventName: "update",
243
+ onMessage: (event) => {
244
+ //
245
+ },
246
+ onError: (error) => {
247
+ //
248
+ },
249
+ onComplete: () => {
250
+ //
251
+ },
252
+ endSignal: "</stream>",
253
+ glue: " ",
254
+ replace: false,
255
+ });
256
+ </script>
257
+ ```
258
+
259
+ You can close the connection manually by using the returned `close` function:
260
+
261
+ ```svelte
262
+ <script>
263
+ import { useEventStream } from "@laravel/stream-svelte";
264
+ import { onMount } from "svelte";
265
+
266
+ const eventStream = useEventStream("/stream");
267
+
268
+ onMount(() => {
269
+ const timeout = setTimeout(() => {
270
+ eventStream.close();
271
+ }, 3000);
272
+ return () => clearTimeout(timeout);
273
+ });
274
+ </script>
275
+
276
+ <div>{$eventStream.message}</div>
277
+ ```
278
+
279
+ The `clearMessage` function may be used to clear the message content that has been received so far:
280
+
281
+ ```svelte
282
+ <script>
283
+ import { useEventStream } from "@laravel/stream-svelte";
284
+ import { onMount } from "svelte";
285
+
286
+ const eventStream = useEventStream("/stream");
287
+
288
+ onMount(() => {
289
+ const timeout = setTimeout(() => {
290
+ eventStream.clearMessage();
291
+ }, 3000);
292
+ return () => clearTimeout(timeout);
293
+ });
294
+ </script>
295
+
296
+ <div>{$eventStream.message}</div>
297
+ ```
298
+
299
+ ## License
300
+
301
+ Laravel Stream is open-sourced software licensed under the [MIT license](LICENSE.md).
@@ -0,0 +1,108 @@
1
+ import { Subscriber } from 'svelte/store';
2
+ import { Unsubscriber } from 'svelte/store';
3
+
4
+ export declare type EventStreamOptions = {
5
+ eventName?: string | string[];
6
+ endSignal?: string;
7
+ glue?: string;
8
+ replace?: boolean;
9
+ onMessage?: (event: MessageEvent) => void;
10
+ onComplete?: () => void;
11
+ onError?: (error: Error) => void;
12
+ };
13
+
14
+ export declare type EventStreamResult = {
15
+ subscribe: (run: (value: EventStreamState) => void) => () => void;
16
+ close: (resetMessage?: boolean) => void;
17
+ clearMessage: () => void;
18
+ };
19
+
20
+ export declare type EventStreamState = {
21
+ message: string;
22
+ messageParts: readonly string[];
23
+ };
24
+
25
+ export declare type JsonStreamState<TJsonData = null> = {
26
+ data: TJsonData | null;
27
+ strData: string;
28
+ isFetching: boolean;
29
+ isStreaming: boolean;
30
+ };
31
+
32
+ export declare type Stream<TJsonData = null, TSendBody extends Record<string, any> = {}> = {
33
+ subscribe: (run: (value: StreamState<TJsonData>) => void) => () => void;
34
+ id: string;
35
+ send: (body?: TSendBody) => void;
36
+ cancel: () => void;
37
+ clearData: () => void;
38
+ };
39
+
40
+ export declare type StreamMeta<TJsonData = null> = {
41
+ controller: AbortController;
42
+ data: string;
43
+ isFetching: boolean;
44
+ isStreaming: boolean;
45
+ jsonData: TJsonData | null;
46
+ };
47
+
48
+ export declare type StreamOptions<TSendBody extends Record<string, any> = {}> = {
49
+ id?: string;
50
+ initialInput?: TSendBody;
51
+ headers?: Record<string, string>;
52
+ csrfToken?: string;
53
+ json?: boolean;
54
+ credentials?: RequestCredentials;
55
+ onResponse?: (response: Response) => void;
56
+ onData?: (data: string) => void;
57
+ onCancel?: () => void;
58
+ onFinish?: () => void;
59
+ onError?: (error: Error) => void;
60
+ onBeforeSend?: (request: RequestInit) => boolean | RequestInit | void;
61
+ };
62
+
63
+ export declare type StreamState<TJsonData = null> = {
64
+ data: string;
65
+ jsonData: TJsonData | null;
66
+ isFetching: boolean;
67
+ isStreaming: boolean;
68
+ };
69
+
70
+ /**
71
+ * Creates a reactive event stream for handling server-sent events (SSE) in Svelte 5.
72
+ * Returns a Svelte store: use `$eventStream` in templates so the component re-renders when events arrive.
73
+ *
74
+ * @param url - The URL to connect to for the EventSource (can be a function for reactivity)
75
+ * @param options - Options for the stream
76
+ *
77
+ * @link https://laravel.com/docs/responses#event-streams
78
+ *
79
+ * @returns A store-like object: subscribe to react to changes, plus close and clearMessage
80
+ */
81
+ export declare const useEventStream: (url: string | (() => string), { eventName, endSignal, glue, replace, onMessage, onComplete, onError, }?: EventStreamOptions) => EventStreamResult;
82
+
83
+ export declare const useJsonStream: <TJsonData = null, TSendBody extends Record<string, any> = {}>(url: string | (() => string), options?: Omit<StreamOptions<TSendBody>, "json">) => {
84
+ subscribe: (this: void, run: Subscriber< {
85
+ data: TJsonData | null;
86
+ strData: string;
87
+ isFetching: boolean;
88
+ isStreaming: boolean;
89
+ }>, invalidate?: () => void) => Unsubscriber;
90
+ id: string;
91
+ send: (body?: TSendBody | undefined) => void;
92
+ cancel: () => void;
93
+ clearData: () => void;
94
+ };
95
+
96
+ /**
97
+ * Creates a reactive stream for handling streaming responses from Laravel.
98
+ * Returns a Svelte store: use `$stream` in templates so the component re-renders when data updates.
99
+ *
100
+ * @param url - The URL to POST to (or a getter for reactive URLs)
101
+ * @param options - Stream options (initialInput, callbacks, etc.)
102
+ * @returns A store-like object: subscribe to react to changes, plus send, cancel, clearData, id
103
+ *
104
+ * @see https://laravel.com/docs/responses#streamed-responses
105
+ */
106
+ export declare const useStream: <TSendBody extends Record<string, any> = {}, TJsonData = null>(url: string | (() => string), options?: StreamOptions<TSendBody>) => Stream<TJsonData, TSendBody>;
107
+
108
+ export { }