@intrig/plugin-react 0.0.1

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.
Files changed (55) hide show
  1. package/.swcrc +29 -0
  2. package/README.md +7 -0
  3. package/eslint.config.mjs +19 -0
  4. package/package.json +25 -0
  5. package/project.json +29 -0
  6. package/rollup.config.cjs +54 -0
  7. package/rollup.config.mjs +33 -0
  8. package/src/index.ts +2 -0
  9. package/src/lib/code-generator.ts +79 -0
  10. package/src/lib/get-endpoint-documentation.ts +35 -0
  11. package/src/lib/get-schema-documentation.ts +11 -0
  12. package/src/lib/internal-types.ts +15 -0
  13. package/src/lib/plugin-react.ts +22 -0
  14. package/src/lib/templates/context.template.ts +74 -0
  15. package/src/lib/templates/docs/__snapshots__/async-hook.spec.ts.snap +889 -0
  16. package/src/lib/templates/docs/__snapshots__/download-hook.spec.ts.snap +1445 -0
  17. package/src/lib/templates/docs/__snapshots__/react-hook.spec.ts.snap +1371 -0
  18. package/src/lib/templates/docs/__snapshots__/sse-hook.spec.ts.snap +2008 -0
  19. package/src/lib/templates/docs/async-hook.spec.ts +92 -0
  20. package/src/lib/templates/docs/async-hook.ts +226 -0
  21. package/src/lib/templates/docs/download-hook.spec.ts +182 -0
  22. package/src/lib/templates/docs/download-hook.ts +170 -0
  23. package/src/lib/templates/docs/react-hook.spec.ts +97 -0
  24. package/src/lib/templates/docs/react-hook.ts +323 -0
  25. package/src/lib/templates/docs/schema.ts +105 -0
  26. package/src/lib/templates/docs/sse-hook.spec.ts +207 -0
  27. package/src/lib/templates/docs/sse-hook.ts +221 -0
  28. package/src/lib/templates/extra.template.ts +198 -0
  29. package/src/lib/templates/index.template.ts +14 -0
  30. package/src/lib/templates/intrigMiddleware.template.ts +21 -0
  31. package/src/lib/templates/logger.template.ts +67 -0
  32. package/src/lib/templates/media-type-utils.template.ts +191 -0
  33. package/src/lib/templates/network-state.template.ts +702 -0
  34. package/src/lib/templates/packageJson.template.ts +63 -0
  35. package/src/lib/templates/provider/__tests__/provider-templates.spec.ts +209 -0
  36. package/src/lib/templates/provider/axios-config.template.ts +49 -0
  37. package/src/lib/templates/provider/hooks.template.ts +240 -0
  38. package/src/lib/templates/provider/interfaces.template.ts +72 -0
  39. package/src/lib/templates/provider/intrig-provider-stub.template.ts +73 -0
  40. package/src/lib/templates/provider/intrig-provider.template.ts +185 -0
  41. package/src/lib/templates/provider/main.template.ts +48 -0
  42. package/src/lib/templates/provider/reducer.template.ts +50 -0
  43. package/src/lib/templates/provider/status-trap.template.ts +80 -0
  44. package/src/lib/templates/provider.template.ts +698 -0
  45. package/src/lib/templates/source/controller/method/asyncFunctionHook.template.ts +196 -0
  46. package/src/lib/templates/source/controller/method/clientIndex.template.ts +38 -0
  47. package/src/lib/templates/source/controller/method/download.template.ts +256 -0
  48. package/src/lib/templates/source/controller/method/params.template.ts +31 -0
  49. package/src/lib/templates/source/controller/method/requestHook.template.ts +220 -0
  50. package/src/lib/templates/source/type/typeTemplate.ts +257 -0
  51. package/src/lib/templates/swcrc.template.ts +25 -0
  52. package/src/lib/templates/tsconfig.template.ts +37 -0
  53. package/src/lib/templates/type-utils.template.ts +28 -0
  54. package/tsconfig.json +13 -0
  55. package/tsconfig.lib.json +20 -0
@@ -0,0 +1,2008 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`reactSseHookDocs > handles case-insensitive path parameter detection 1`] = `
4
+ "# Intrig SSE Hooks — Quick Guide
5
+
6
+ ## When should I use the SSE hook?
7
+
8
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
9
+ - **You only need a final result** → use the regular **stateful hook**.
10
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
11
+
12
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
13
+
14
+ ---
15
+
16
+ ## Copy-paste starter (fast lane)
17
+
18
+ ### 1) Hook import
19
+
20
+ \`\`\`ts
21
+ import { useStreamOrderUpdates } from "@intrig/react/orders/{orderId}/client";
22
+ \`\`\`
23
+
24
+ ### 2) Utility guards
25
+
26
+ \`\`\`ts
27
+ import { isPending, isSuccess, isError } from "@intrig/react";
28
+ \`\`\`
29
+
30
+ ### 3) Hook instance (auto-clear on unmount)
31
+
32
+ \`\`\`ts
33
+ const [streamOrderUpdatesResp, streamOrderUpdates] = useStreamOrderUpdates({
34
+ clearOnUnmount: true,
35
+ });
36
+ \`\`\`
37
+
38
+ ---
39
+
40
+ ## TL;DR (copy–paste)
41
+
42
+ \`\`\`tsx
43
+ import { useStreamOrderUpdates } from "@intrig/react/orders/{orderId}/client";
44
+ import { isPending, isSuccess, isError } from "@intrig/react";
45
+ import { useEffect, useState } from "react";
46
+
47
+ export default function Example() {
48
+ const [streamOrderUpdatesResp, streamOrderUpdates] = useStreamOrderUpdates({
49
+ clearOnUnmount: true,
50
+ });
51
+ const [messages, setMessages] = useState<any[]>([]);
52
+
53
+ useEffect(() => {
54
+ streamOrderUpdates(streamOrderUpdatesParams); // start stream
55
+ }, [streamOrderUpdates]);
56
+
57
+ useEffect(() => {
58
+ // SSE delivers messages while state is Pending
59
+ if (isPending(streamOrderUpdatesResp)) {
60
+ setMessages((prev) => [...prev, streamOrderUpdatesResp.data]);
61
+ }
62
+ }, [streamOrderUpdatesResp]);
63
+
64
+ if (isError(streamOrderUpdatesResp)) return <>An error occurred</>;
65
+ if (isPending(streamOrderUpdatesResp))
66
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
67
+ if (isSuccess(streamOrderUpdatesResp)) return <>Completed</>;
68
+
69
+ return null;
70
+ }
71
+ \`\`\`
72
+
73
+ ### Optional types (if generated by your build)
74
+
75
+ \`\`\`ts
76
+ import type { StreamOrderUpdatesParams } from "@intrig/react/orders/{orderId}/StreamOrderUpdates.params";
77
+ import type { StreamOrderUpdatesResponseBody } from "@intrig/react/orders/{orderId}/StreamOrderUpdates.response";
78
+ \`\`\`
79
+
80
+ ---
81
+
82
+ ## Hook API
83
+
84
+ \`\`\`ts
85
+ // Signature (shape shown; concrete generics vary per generated hook)
86
+ declare function useStreamOrderUpdates(options?: {
87
+ fetchOnMount?: boolean;
88
+ clearOnUnmount?: boolean; // recommended for streams
89
+ key?: string; // isolate multiple subscriptions
90
+ params?: StreamOrderUpdatesParams;
91
+ body?: unknown;
92
+ }): [
93
+ // While streaming: isPending(state) === true and state.data is the latest event
94
+ NetworkState<StreamOrderUpdatesResponseBody /* or event payload type */, any>,
95
+ // Start streaming:
96
+ (req: { params?: StreamOrderUpdatesParams; body?: unknown }) => void,
97
+ // Clear/close stream:
98
+ () => void,
99
+ ];
100
+ \`\`\`
101
+
102
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamOrderUpdatesResp.data\` **only while** \`isPending(streamOrderUpdatesResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
103
+
104
+ ---
105
+
106
+ ## Usage patterns
107
+
108
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
109
+
110
+ \`\`\`tsx
111
+ const [streamOrderUpdatesResp, streamOrderUpdates] = useStreamOrderUpdates({
112
+ clearOnUnmount: true,
113
+ });
114
+
115
+ useEffect(() => {
116
+ streamOrderUpdates(streamOrderUpdatesParams);
117
+ }, [streamOrderUpdates]);
118
+ \`\`\`
119
+
120
+ <details><summary>Description</summary>
121
+ Starts the stream when the component mounts and closes it when the component unmounts.
122
+ </details>
123
+
124
+ ### 2) Collect messages into an array (simple collector)
125
+
126
+ \`\`\`tsx
127
+ const [messages, setMessages] = useState<any[]>([]);
128
+
129
+ useEffect(() => {
130
+ if (isPending(streamOrderUpdatesResp))
131
+ setMessages((m) => [...m, streamOrderUpdatesResp.data]);
132
+ }, [streamOrderUpdatesResp]);
133
+ \`\`\`
134
+
135
+ <details><summary>Description</summary>
136
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
137
+ </details>
138
+
139
+ ### 3) Keep only the latest event (cheap UI)
140
+
141
+ \`\`\`tsx
142
+ const latest = isPending(streamOrderUpdatesResp)
143
+ ? streamOrderUpdatesResp.data
144
+ : undefined;
145
+ \`\`\`
146
+
147
+ <details><summary>Description</summary>
148
+ When you only need the most recent message (progress percentage, status line).
149
+ </details>
150
+
151
+ ### 4) Controlled start/stop (user-triggered)
152
+
153
+ \`\`\`tsx
154
+ const [streamOrderUpdatesResp, streamOrderUpdates, clearStreamOrderUpdates] =
155
+ useStreamOrderUpdates();
156
+
157
+ const start = () => streamOrderUpdates(streamOrderUpdatesParams);
158
+ const stop = () => clearStreamOrderUpdates();
159
+ \`\`\`
160
+
161
+ <details><summary>Description</summary>
162
+ Expose play/pause UI for long streams or admin tools.
163
+ </details>
164
+
165
+ ---
166
+
167
+ ## Full example (with flushSync option)
168
+
169
+ \`\`\`tsx
170
+ import { useStreamOrderUpdates } from "@intrig/react/orders/{orderId}/client";
171
+ import { isPending, isSuccess, isError } from "@intrig/react";
172
+ import { useEffect, useState } from "react";
173
+ import { flushSync } from "react-dom";
174
+
175
+ function MyComponent() {
176
+ const [streamOrderUpdatesResp, streamOrderUpdates] = useStreamOrderUpdates({
177
+ clearOnUnmount: true,
178
+ });
179
+ const [events, setEvents] = useState<any[]>([]);
180
+
181
+ useEffect(() => {
182
+ streamOrderUpdates(streamOrderUpdatesParams);
183
+ }, [streamOrderUpdates]);
184
+
185
+ useEffect(() => {
186
+ if (isPending(streamOrderUpdatesResp)) {
187
+ // Use flushSync only if you must render every single event (high-frequency streams).
188
+ flushSync(() => setEvents((xs) => [...xs, streamOrderUpdatesResp.data]));
189
+ }
190
+ }, [streamOrderUpdatesResp]);
191
+
192
+ if (isError(streamOrderUpdatesResp)) return <>Stream error</>;
193
+ return (
194
+ <>
195
+ {isPending(streamOrderUpdatesResp) && (
196
+ <pre>{JSON.stringify(events, null, 2)}</pre>
197
+ )}
198
+ {isSuccess(streamOrderUpdatesResp) && (
199
+ <>Completed ({events.length} events)</>
200
+ )}
201
+ </>
202
+ );
203
+ }
204
+ \`\`\`
205
+
206
+ ---
207
+
208
+ ## Tips, anti-patterns & gotchas
209
+
210
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
211
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
212
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
213
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
214
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
215
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
216
+
217
+ ---
218
+
219
+ ## Troubleshooting
220
+
221
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
222
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
223
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamOrderUpdates\` when appropriate.
224
+ "
225
+ `;
226
+
227
+ exports[`reactSseHookDocs > handles complex SSE endpoint with body and multiple params 1`] = `
228
+ "# Intrig SSE Hooks — Quick Guide
229
+
230
+ ## When should I use the SSE hook?
231
+
232
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
233
+ - **You only need a final result** → use the regular **stateful hook**.
234
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
235
+
236
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
237
+
238
+ ---
239
+
240
+ ## Copy-paste starter (fast lane)
241
+
242
+ ### 1) Hook import
243
+
244
+ \`\`\`ts
245
+ import { useStreamAnalytics } from "@intrig/react/analytics/{dashboardId}/stream/client";
246
+ \`\`\`
247
+
248
+ ### 2) Utility guards
249
+
250
+ \`\`\`ts
251
+ import { isPending, isSuccess, isError } from "@intrig/react";
252
+ \`\`\`
253
+
254
+ ### 3) Hook instance (auto-clear on unmount)
255
+
256
+ \`\`\`ts
257
+ const [streamAnalyticsResp, streamAnalytics] = useStreamAnalytics({
258
+ clearOnUnmount: true,
259
+ });
260
+ \`\`\`
261
+
262
+ ---
263
+
264
+ ## TL;DR (copy–paste)
265
+
266
+ \`\`\`tsx
267
+ import { useStreamAnalytics } from "@intrig/react/analytics/{dashboardId}/stream/client";
268
+ import { isPending, isSuccess, isError } from "@intrig/react";
269
+ import { useEffect, useState } from "react";
270
+
271
+ export default function Example() {
272
+ const [streamAnalyticsResp, streamAnalytics] = useStreamAnalytics({
273
+ clearOnUnmount: true,
274
+ });
275
+ const [messages, setMessages] = useState<any[]>([]);
276
+
277
+ useEffect(() => {
278
+ streamAnalytics(streamAnalyticsRequest, streamAnalyticsParams); // start stream
279
+ }, [streamAnalytics]);
280
+
281
+ useEffect(() => {
282
+ // SSE delivers messages while state is Pending
283
+ if (isPending(streamAnalyticsResp)) {
284
+ setMessages((prev) => [...prev, streamAnalyticsResp.data]);
285
+ }
286
+ }, [streamAnalyticsResp]);
287
+
288
+ if (isError(streamAnalyticsResp)) return <>An error occurred</>;
289
+ if (isPending(streamAnalyticsResp))
290
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
291
+ if (isSuccess(streamAnalyticsResp)) return <>Completed</>;
292
+
293
+ return null;
294
+ }
295
+ \`\`\`
296
+
297
+ ### Optional types (if generated by your build)
298
+
299
+ \`\`\`ts
300
+ import type { StreamAnalyticsRequest } from "@intrig/react/demo_api/components/schemas/StreamAnalyticsRequest";
301
+ import type { StreamAnalyticsParams } from "@intrig/react/analytics/{dashboardId}/stream/StreamAnalytics.params";
302
+ import type { StreamAnalyticsResponseBody } from "@intrig/react/analytics/{dashboardId}/stream/StreamAnalytics.response";
303
+ \`\`\`
304
+
305
+ ---
306
+
307
+ ## Hook API
308
+
309
+ \`\`\`ts
310
+ // Signature (shape shown; concrete generics vary per generated hook)
311
+ declare function useStreamAnalytics(options?: {
312
+ fetchOnMount?: boolean;
313
+ clearOnUnmount?: boolean; // recommended for streams
314
+ key?: string; // isolate multiple subscriptions
315
+ params?: StreamAnalyticsParams;
316
+ body?: StreamAnalyticsRequest;
317
+ }): [
318
+ // While streaming: isPending(state) === true and state.data is the latest event
319
+ NetworkState<StreamAnalyticsResponseBody /* or event payload type */, any>,
320
+ // Start streaming:
321
+ (req: {
322
+ params?: StreamAnalyticsParams;
323
+ body?: StreamAnalyticsRequest;
324
+ }) => void,
325
+ // Clear/close stream:
326
+ () => void,
327
+ ];
328
+ \`\`\`
329
+
330
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamAnalyticsResp.data\` **only while** \`isPending(streamAnalyticsResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
331
+
332
+ ---
333
+
334
+ ## Usage patterns
335
+
336
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
337
+
338
+ \`\`\`tsx
339
+ const [streamAnalyticsResp, streamAnalytics] = useStreamAnalytics({
340
+ clearOnUnmount: true,
341
+ });
342
+
343
+ useEffect(() => {
344
+ streamAnalytics(streamAnalyticsRequest, streamAnalyticsParams);
345
+ }, [streamAnalytics]);
346
+ \`\`\`
347
+
348
+ <details><summary>Description</summary>
349
+ Starts the stream when the component mounts and closes it when the component unmounts.
350
+ </details>
351
+
352
+ ### 2) Collect messages into an array (simple collector)
353
+
354
+ \`\`\`tsx
355
+ const [messages, setMessages] = useState<any[]>([]);
356
+
357
+ useEffect(() => {
358
+ if (isPending(streamAnalyticsResp))
359
+ setMessages((m) => [...m, streamAnalyticsResp.data]);
360
+ }, [streamAnalyticsResp]);
361
+ \`\`\`
362
+
363
+ <details><summary>Description</summary>
364
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
365
+ </details>
366
+
367
+ ### 3) Keep only the latest event (cheap UI)
368
+
369
+ \`\`\`tsx
370
+ const latest = isPending(streamAnalyticsResp)
371
+ ? streamAnalyticsResp.data
372
+ : undefined;
373
+ \`\`\`
374
+
375
+ <details><summary>Description</summary>
376
+ When you only need the most recent message (progress percentage, status line).
377
+ </details>
378
+
379
+ ### 4) Controlled start/stop (user-triggered)
380
+
381
+ \`\`\`tsx
382
+ const [streamAnalyticsResp, streamAnalytics, clearStreamAnalytics] =
383
+ useStreamAnalytics();
384
+
385
+ const start = () =>
386
+ streamAnalytics(streamAnalyticsRequest, streamAnalyticsParams);
387
+ const stop = () => clearStreamAnalytics();
388
+ \`\`\`
389
+
390
+ <details><summary>Description</summary>
391
+ Expose play/pause UI for long streams or admin tools.
392
+ </details>
393
+
394
+ ---
395
+
396
+ ## Full example (with flushSync option)
397
+
398
+ \`\`\`tsx
399
+ import { useStreamAnalytics } from "@intrig/react/analytics/{dashboardId}/stream/client";
400
+ import { isPending, isSuccess, isError } from "@intrig/react";
401
+ import { useEffect, useState } from "react";
402
+ import { flushSync } from "react-dom";
403
+
404
+ function MyComponent() {
405
+ const [streamAnalyticsResp, streamAnalytics] = useStreamAnalytics({
406
+ clearOnUnmount: true,
407
+ });
408
+ const [events, setEvents] = useState<any[]>([]);
409
+
410
+ useEffect(() => {
411
+ streamAnalytics(streamAnalyticsRequest, streamAnalyticsParams);
412
+ }, [streamAnalytics]);
413
+
414
+ useEffect(() => {
415
+ if (isPending(streamAnalyticsResp)) {
416
+ // Use flushSync only if you must render every single event (high-frequency streams).
417
+ flushSync(() => setEvents((xs) => [...xs, streamAnalyticsResp.data]));
418
+ }
419
+ }, [streamAnalyticsResp]);
420
+
421
+ if (isError(streamAnalyticsResp)) return <>Stream error</>;
422
+ return (
423
+ <>
424
+ {isPending(streamAnalyticsResp) && (
425
+ <pre>{JSON.stringify(events, null, 2)}</pre>
426
+ )}
427
+ {isSuccess(streamAnalyticsResp) && (
428
+ <>Completed ({events.length} events)</>
429
+ )}
430
+ </>
431
+ );
432
+ }
433
+ \`\`\`
434
+
435
+ ---
436
+
437
+ ## Tips, anti-patterns & gotchas
438
+
439
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
440
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
441
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
442
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
443
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
444
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
445
+
446
+ ---
447
+
448
+ ## Troubleshooting
449
+
450
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
451
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
452
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamAnalytics\` when appropriate.
453
+ "
454
+ `;
455
+
456
+ exports[`reactSseHookDocs > handles empty variables array 1`] = `
457
+ "# Intrig SSE Hooks — Quick Guide
458
+
459
+ ## When should I use the SSE hook?
460
+
461
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
462
+ - **You only need a final result** → use the regular **stateful hook**.
463
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
464
+
465
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
466
+
467
+ ---
468
+
469
+ ## Copy-paste starter (fast lane)
470
+
471
+ ### 1) Hook import
472
+
473
+ \`\`\`ts
474
+ import { useStreamGlobalEvents } from "@intrig/react/events/client";
475
+ \`\`\`
476
+
477
+ ### 2) Utility guards
478
+
479
+ \`\`\`ts
480
+ import { isPending, isSuccess, isError } from "@intrig/react";
481
+ \`\`\`
482
+
483
+ ### 3) Hook instance (auto-clear on unmount)
484
+
485
+ \`\`\`ts
486
+ const [streamGlobalEventsResp, streamGlobalEvents] = useStreamGlobalEvents({
487
+ clearOnUnmount: true,
488
+ });
489
+ \`\`\`
490
+
491
+ ---
492
+
493
+ ## TL;DR (copy–paste)
494
+
495
+ \`\`\`tsx
496
+ import { useStreamGlobalEvents } from "@intrig/react/events/client";
497
+ import { isPending, isSuccess, isError } from "@intrig/react";
498
+ import { useEffect, useState } from "react";
499
+
500
+ export default function Example() {
501
+ const [streamGlobalEventsResp, streamGlobalEvents] = useStreamGlobalEvents({
502
+ clearOnUnmount: true,
503
+ });
504
+ const [messages, setMessages] = useState<any[]>([]);
505
+
506
+ useEffect(() => {
507
+ streamGlobalEvents({}); // start stream
508
+ }, [streamGlobalEvents]);
509
+
510
+ useEffect(() => {
511
+ // SSE delivers messages while state is Pending
512
+ if (isPending(streamGlobalEventsResp)) {
513
+ setMessages((prev) => [...prev, streamGlobalEventsResp.data]);
514
+ }
515
+ }, [streamGlobalEventsResp]);
516
+
517
+ if (isError(streamGlobalEventsResp)) return <>An error occurred</>;
518
+ if (isPending(streamGlobalEventsResp))
519
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
520
+ if (isSuccess(streamGlobalEventsResp)) return <>Completed</>;
521
+
522
+ return null;
523
+ }
524
+ \`\`\`
525
+
526
+ ---
527
+
528
+ ## Hook API
529
+
530
+ \`\`\`ts
531
+ // Signature (shape shown; concrete generics vary per generated hook)
532
+ declare function useStreamGlobalEvents(options?: {
533
+ fetchOnMount?: boolean;
534
+ clearOnUnmount?: boolean; // recommended for streams
535
+ key?: string; // isolate multiple subscriptions
536
+ params?: unknown;
537
+ body?: unknown;
538
+ }): [
539
+ // While streaming: isPending(state) === true and state.data is the latest event
540
+ NetworkState<StreamGlobalEventsResponseBody /* or event payload type */, any>,
541
+ // Start streaming:
542
+ (req: { params?: unknown; body?: unknown }) => void,
543
+ // Clear/close stream:
544
+ () => void,
545
+ ];
546
+ \`\`\`
547
+
548
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamGlobalEventsResp.data\` **only while** \`isPending(streamGlobalEventsResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
549
+
550
+ ---
551
+
552
+ ## Usage patterns
553
+
554
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
555
+
556
+ \`\`\`tsx
557
+ const [streamGlobalEventsResp, streamGlobalEvents] = useStreamGlobalEvents({
558
+ clearOnUnmount: true,
559
+ });
560
+
561
+ useEffect(() => {
562
+ streamGlobalEvents({});
563
+ }, [streamGlobalEvents]);
564
+ \`\`\`
565
+
566
+ <details><summary>Description</summary>
567
+ Starts the stream when the component mounts and closes it when the component unmounts.
568
+ </details>
569
+
570
+ ### 2) Collect messages into an array (simple collector)
571
+
572
+ \`\`\`tsx
573
+ const [messages, setMessages] = useState<any[]>([]);
574
+
575
+ useEffect(() => {
576
+ if (isPending(streamGlobalEventsResp))
577
+ setMessages((m) => [...m, streamGlobalEventsResp.data]);
578
+ }, [streamGlobalEventsResp]);
579
+ \`\`\`
580
+
581
+ <details><summary>Description</summary>
582
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
583
+ </details>
584
+
585
+ ### 3) Keep only the latest event (cheap UI)
586
+
587
+ \`\`\`tsx
588
+ const latest = isPending(streamGlobalEventsResp)
589
+ ? streamGlobalEventsResp.data
590
+ : undefined;
591
+ \`\`\`
592
+
593
+ <details><summary>Description</summary>
594
+ When you only need the most recent message (progress percentage, status line).
595
+ </details>
596
+
597
+ ### 4) Controlled start/stop (user-triggered)
598
+
599
+ \`\`\`tsx
600
+ const [streamGlobalEventsResp, streamGlobalEvents, clearStreamGlobalEvents] =
601
+ useStreamGlobalEvents();
602
+
603
+ const start = () => streamGlobalEvents({});
604
+ const stop = () => clearStreamGlobalEvents();
605
+ \`\`\`
606
+
607
+ <details><summary>Description</summary>
608
+ Expose play/pause UI for long streams or admin tools.
609
+ </details>
610
+
611
+ ---
612
+
613
+ ## Full example (with flushSync option)
614
+
615
+ \`\`\`tsx
616
+ import { useStreamGlobalEvents } from "@intrig/react/events/client";
617
+ import { isPending, isSuccess, isError } from "@intrig/react";
618
+ import { useEffect, useState } from "react";
619
+ import { flushSync } from "react-dom";
620
+
621
+ function MyComponent() {
622
+ const [streamGlobalEventsResp, streamGlobalEvents] = useStreamGlobalEvents({
623
+ clearOnUnmount: true,
624
+ });
625
+ const [events, setEvents] = useState<any[]>([]);
626
+
627
+ useEffect(() => {
628
+ streamGlobalEvents({});
629
+ }, [streamGlobalEvents]);
630
+
631
+ useEffect(() => {
632
+ if (isPending(streamGlobalEventsResp)) {
633
+ // Use flushSync only if you must render every single event (high-frequency streams).
634
+ flushSync(() => setEvents((xs) => [...xs, streamGlobalEventsResp.data]));
635
+ }
636
+ }, [streamGlobalEventsResp]);
637
+
638
+ if (isError(streamGlobalEventsResp)) return <>Stream error</>;
639
+ return (
640
+ <>
641
+ {isPending(streamGlobalEventsResp) && (
642
+ <pre>{JSON.stringify(events, null, 2)}</pre>
643
+ )}
644
+ {isSuccess(streamGlobalEventsResp) && (
645
+ <>Completed ({events.length} events)</>
646
+ )}
647
+ </>
648
+ );
649
+ }
650
+ \`\`\`
651
+
652
+ ---
653
+
654
+ ## Tips, anti-patterns & gotchas
655
+
656
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
657
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
658
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
659
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
660
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
661
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
662
+
663
+ ---
664
+
665
+ ## Troubleshooting
666
+
667
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
668
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
669
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamGlobalEvents\` when appropriate.
670
+ "
671
+ `;
672
+
673
+ exports[`reactSseHookDocs > handles multiple path params 1`] = `
674
+ "# Intrig SSE Hooks — Quick Guide
675
+
676
+ ## When should I use the SSE hook?
677
+
678
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
679
+ - **You only need a final result** → use the regular **stateful hook**.
680
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
681
+
682
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
683
+
684
+ ---
685
+
686
+ ## Copy-paste starter (fast lane)
687
+
688
+ ### 1) Hook import
689
+
690
+ \`\`\`ts
691
+ import { useStreamProjectNotifications } from "@intrig/react/projects/{projectId}/notifications/{notificationId}/client";
692
+ \`\`\`
693
+
694
+ ### 2) Utility guards
695
+
696
+ \`\`\`ts
697
+ import { isPending, isSuccess, isError } from "@intrig/react";
698
+ \`\`\`
699
+
700
+ ### 3) Hook instance (auto-clear on unmount)
701
+
702
+ \`\`\`ts
703
+ const [streamProjectNotificationsResp, streamProjectNotifications] =
704
+ useStreamProjectNotifications({ clearOnUnmount: true });
705
+ \`\`\`
706
+
707
+ ---
708
+
709
+ ## TL;DR (copy–paste)
710
+
711
+ \`\`\`tsx
712
+ import { useStreamProjectNotifications } from "@intrig/react/projects/{projectId}/notifications/{notificationId}/client";
713
+ import { isPending, isSuccess, isError } from "@intrig/react";
714
+ import { useEffect, useState } from "react";
715
+
716
+ export default function Example() {
717
+ const [streamProjectNotificationsResp, streamProjectNotifications] =
718
+ useStreamProjectNotifications({ clearOnUnmount: true });
719
+ const [messages, setMessages] = useState<any[]>([]);
720
+
721
+ useEffect(() => {
722
+ streamProjectNotifications(streamProjectNotificationsParams); // start stream
723
+ }, [streamProjectNotifications]);
724
+
725
+ useEffect(() => {
726
+ // SSE delivers messages while state is Pending
727
+ if (isPending(streamProjectNotificationsResp)) {
728
+ setMessages((prev) => [...prev, streamProjectNotificationsResp.data]);
729
+ }
730
+ }, [streamProjectNotificationsResp]);
731
+
732
+ if (isError(streamProjectNotificationsResp)) return <>An error occurred</>;
733
+ if (isPending(streamProjectNotificationsResp))
734
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
735
+ if (isSuccess(streamProjectNotificationsResp)) return <>Completed</>;
736
+
737
+ return null;
738
+ }
739
+ \`\`\`
740
+
741
+ ### Optional types (if generated by your build)
742
+
743
+ \`\`\`ts
744
+ import type { StreamProjectNotificationsParams } from "@intrig/react/projects/{projectId}/notifications/{notificationId}/StreamProjectNotifications.params";
745
+ import type { StreamProjectNotificationsResponseBody } from "@intrig/react/projects/{projectId}/notifications/{notificationId}/StreamProjectNotifications.response";
746
+ \`\`\`
747
+
748
+ ---
749
+
750
+ ## Hook API
751
+
752
+ \`\`\`ts
753
+ // Signature (shape shown; concrete generics vary per generated hook)
754
+ declare function useStreamProjectNotifications(options?: {
755
+ fetchOnMount?: boolean;
756
+ clearOnUnmount?: boolean; // recommended for streams
757
+ key?: string; // isolate multiple subscriptions
758
+ params?: StreamProjectNotificationsParams;
759
+ body?: unknown;
760
+ }): [
761
+ // While streaming: isPending(state) === true and state.data is the latest event
762
+ NetworkState<
763
+ StreamProjectNotificationsResponseBody /* or event payload type */,
764
+ any
765
+ >,
766
+ // Start streaming:
767
+ (req: { params?: StreamProjectNotificationsParams; body?: unknown }) => void,
768
+ // Clear/close stream:
769
+ () => void,
770
+ ];
771
+ \`\`\`
772
+
773
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamProjectNotificationsResp.data\` **only while** \`isPending(streamProjectNotificationsResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
774
+
775
+ ---
776
+
777
+ ## Usage patterns
778
+
779
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
780
+
781
+ \`\`\`tsx
782
+ const [streamProjectNotificationsResp, streamProjectNotifications] =
783
+ useStreamProjectNotifications({ clearOnUnmount: true });
784
+
785
+ useEffect(() => {
786
+ streamProjectNotifications(streamProjectNotificationsParams);
787
+ }, [streamProjectNotifications]);
788
+ \`\`\`
789
+
790
+ <details><summary>Description</summary>
791
+ Starts the stream when the component mounts and closes it when the component unmounts.
792
+ </details>
793
+
794
+ ### 2) Collect messages into an array (simple collector)
795
+
796
+ \`\`\`tsx
797
+ const [messages, setMessages] = useState<any[]>([]);
798
+
799
+ useEffect(() => {
800
+ if (isPending(streamProjectNotificationsResp))
801
+ setMessages((m) => [...m, streamProjectNotificationsResp.data]);
802
+ }, [streamProjectNotificationsResp]);
803
+ \`\`\`
804
+
805
+ <details><summary>Description</summary>
806
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
807
+ </details>
808
+
809
+ ### 3) Keep only the latest event (cheap UI)
810
+
811
+ \`\`\`tsx
812
+ const latest = isPending(streamProjectNotificationsResp)
813
+ ? streamProjectNotificationsResp.data
814
+ : undefined;
815
+ \`\`\`
816
+
817
+ <details><summary>Description</summary>
818
+ When you only need the most recent message (progress percentage, status line).
819
+ </details>
820
+
821
+ ### 4) Controlled start/stop (user-triggered)
822
+
823
+ \`\`\`tsx
824
+ const [
825
+ streamProjectNotificationsResp,
826
+ streamProjectNotifications,
827
+ clearStreamProjectNotifications,
828
+ ] = useStreamProjectNotifications();
829
+
830
+ const start = () =>
831
+ streamProjectNotifications(streamProjectNotificationsParams);
832
+ const stop = () => clearStreamProjectNotifications();
833
+ \`\`\`
834
+
835
+ <details><summary>Description</summary>
836
+ Expose play/pause UI for long streams or admin tools.
837
+ </details>
838
+
839
+ ---
840
+
841
+ ## Full example (with flushSync option)
842
+
843
+ \`\`\`tsx
844
+ import { useStreamProjectNotifications } from "@intrig/react/projects/{projectId}/notifications/{notificationId}/client";
845
+ import { isPending, isSuccess, isError } from "@intrig/react";
846
+ import { useEffect, useState } from "react";
847
+ import { flushSync } from "react-dom";
848
+
849
+ function MyComponent() {
850
+ const [streamProjectNotificationsResp, streamProjectNotifications] =
851
+ useStreamProjectNotifications({ clearOnUnmount: true });
852
+ const [events, setEvents] = useState<any[]>([]);
853
+
854
+ useEffect(() => {
855
+ streamProjectNotifications(streamProjectNotificationsParams);
856
+ }, [streamProjectNotifications]);
857
+
858
+ useEffect(() => {
859
+ if (isPending(streamProjectNotificationsResp)) {
860
+ // Use flushSync only if you must render every single event (high-frequency streams).
861
+ flushSync(() =>
862
+ setEvents((xs) => [...xs, streamProjectNotificationsResp.data]),
863
+ );
864
+ }
865
+ }, [streamProjectNotificationsResp]);
866
+
867
+ if (isError(streamProjectNotificationsResp)) return <>Stream error</>;
868
+ return (
869
+ <>
870
+ {isPending(streamProjectNotificationsResp) && (
871
+ <pre>{JSON.stringify(events, null, 2)}</pre>
872
+ )}
873
+ {isSuccess(streamProjectNotificationsResp) && (
874
+ <>Completed ({events.length} events)</>
875
+ )}
876
+ </>
877
+ );
878
+ }
879
+ \`\`\`
880
+
881
+ ---
882
+
883
+ ## Tips, anti-patterns & gotchas
884
+
885
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
886
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
887
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
888
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
889
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
890
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
891
+
892
+ ---
893
+
894
+ ## Troubleshooting
895
+
896
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
897
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
898
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamProjectNotifications\` when appropriate.
899
+ "
900
+ `;
901
+
902
+ exports[`reactSseHookDocs > handles query params mixed with path params 1`] = `
903
+ "# Intrig SSE Hooks — Quick Guide
904
+
905
+ ## When should I use the SSE hook?
906
+
907
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
908
+ - **You only need a final result** → use the regular **stateful hook**.
909
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
910
+
911
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
912
+
913
+ ---
914
+
915
+ ## Copy-paste starter (fast lane)
916
+
917
+ ### 1) Hook import
918
+
919
+ \`\`\`ts
920
+ import { useStreamSystemMetrics } from "@intrig/react/systems/{systemId}/metrics/client";
921
+ \`\`\`
922
+
923
+ ### 2) Utility guards
924
+
925
+ \`\`\`ts
926
+ import { isPending, isSuccess, isError } from "@intrig/react";
927
+ \`\`\`
928
+
929
+ ### 3) Hook instance (auto-clear on unmount)
930
+
931
+ \`\`\`ts
932
+ const [streamSystemMetricsResp, streamSystemMetrics] = useStreamSystemMetrics({
933
+ clearOnUnmount: true,
934
+ });
935
+ \`\`\`
936
+
937
+ ---
938
+
939
+ ## TL;DR (copy–paste)
940
+
941
+ \`\`\`tsx
942
+ import { useStreamSystemMetrics } from "@intrig/react/systems/{systemId}/metrics/client";
943
+ import { isPending, isSuccess, isError } from "@intrig/react";
944
+ import { useEffect, useState } from "react";
945
+
946
+ export default function Example() {
947
+ const [streamSystemMetricsResp, streamSystemMetrics] = useStreamSystemMetrics(
948
+ { clearOnUnmount: true },
949
+ );
950
+ const [messages, setMessages] = useState<any[]>([]);
951
+
952
+ useEffect(() => {
953
+ streamSystemMetrics(streamSystemMetricsParams); // start stream
954
+ }, [streamSystemMetrics]);
955
+
956
+ useEffect(() => {
957
+ // SSE delivers messages while state is Pending
958
+ if (isPending(streamSystemMetricsResp)) {
959
+ setMessages((prev) => [...prev, streamSystemMetricsResp.data]);
960
+ }
961
+ }, [streamSystemMetricsResp]);
962
+
963
+ if (isError(streamSystemMetricsResp)) return <>An error occurred</>;
964
+ if (isPending(streamSystemMetricsResp))
965
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
966
+ if (isSuccess(streamSystemMetricsResp)) return <>Completed</>;
967
+
968
+ return null;
969
+ }
970
+ \`\`\`
971
+
972
+ ### Optional types (if generated by your build)
973
+
974
+ \`\`\`ts
975
+ import type { StreamSystemMetricsParams } from "@intrig/react/systems/{systemId}/metrics/StreamSystemMetrics.params";
976
+ import type { StreamSystemMetricsResponseBody } from "@intrig/react/systems/{systemId}/metrics/StreamSystemMetrics.response";
977
+ \`\`\`
978
+
979
+ ---
980
+
981
+ ## Hook API
982
+
983
+ \`\`\`ts
984
+ // Signature (shape shown; concrete generics vary per generated hook)
985
+ declare function useStreamSystemMetrics(options?: {
986
+ fetchOnMount?: boolean;
987
+ clearOnUnmount?: boolean; // recommended for streams
988
+ key?: string; // isolate multiple subscriptions
989
+ params?: StreamSystemMetricsParams;
990
+ body?: unknown;
991
+ }): [
992
+ // While streaming: isPending(state) === true and state.data is the latest event
993
+ NetworkState<
994
+ StreamSystemMetricsResponseBody /* or event payload type */,
995
+ any
996
+ >,
997
+ // Start streaming:
998
+ (req: { params?: StreamSystemMetricsParams; body?: unknown }) => void,
999
+ // Clear/close stream:
1000
+ () => void,
1001
+ ];
1002
+ \`\`\`
1003
+
1004
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamSystemMetricsResp.data\` **only while** \`isPending(streamSystemMetricsResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
1005
+
1006
+ ---
1007
+
1008
+ ## Usage patterns
1009
+
1010
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
1011
+
1012
+ \`\`\`tsx
1013
+ const [streamSystemMetricsResp, streamSystemMetrics] = useStreamSystemMetrics({
1014
+ clearOnUnmount: true,
1015
+ });
1016
+
1017
+ useEffect(() => {
1018
+ streamSystemMetrics(streamSystemMetricsParams);
1019
+ }, [streamSystemMetrics]);
1020
+ \`\`\`
1021
+
1022
+ <details><summary>Description</summary>
1023
+ Starts the stream when the component mounts and closes it when the component unmounts.
1024
+ </details>
1025
+
1026
+ ### 2) Collect messages into an array (simple collector)
1027
+
1028
+ \`\`\`tsx
1029
+ const [messages, setMessages] = useState<any[]>([]);
1030
+
1031
+ useEffect(() => {
1032
+ if (isPending(streamSystemMetricsResp))
1033
+ setMessages((m) => [...m, streamSystemMetricsResp.data]);
1034
+ }, [streamSystemMetricsResp]);
1035
+ \`\`\`
1036
+
1037
+ <details><summary>Description</summary>
1038
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
1039
+ </details>
1040
+
1041
+ ### 3) Keep only the latest event (cheap UI)
1042
+
1043
+ \`\`\`tsx
1044
+ const latest = isPending(streamSystemMetricsResp)
1045
+ ? streamSystemMetricsResp.data
1046
+ : undefined;
1047
+ \`\`\`
1048
+
1049
+ <details><summary>Description</summary>
1050
+ When you only need the most recent message (progress percentage, status line).
1051
+ </details>
1052
+
1053
+ ### 4) Controlled start/stop (user-triggered)
1054
+
1055
+ \`\`\`tsx
1056
+ const [streamSystemMetricsResp, streamSystemMetrics, clearStreamSystemMetrics] =
1057
+ useStreamSystemMetrics();
1058
+
1059
+ const start = () => streamSystemMetrics(streamSystemMetricsParams);
1060
+ const stop = () => clearStreamSystemMetrics();
1061
+ \`\`\`
1062
+
1063
+ <details><summary>Description</summary>
1064
+ Expose play/pause UI for long streams or admin tools.
1065
+ </details>
1066
+
1067
+ ---
1068
+
1069
+ ## Full example (with flushSync option)
1070
+
1071
+ \`\`\`tsx
1072
+ import { useStreamSystemMetrics } from "@intrig/react/systems/{systemId}/metrics/client";
1073
+ import { isPending, isSuccess, isError } from "@intrig/react";
1074
+ import { useEffect, useState } from "react";
1075
+ import { flushSync } from "react-dom";
1076
+
1077
+ function MyComponent() {
1078
+ const [streamSystemMetricsResp, streamSystemMetrics] = useStreamSystemMetrics(
1079
+ { clearOnUnmount: true },
1080
+ );
1081
+ const [events, setEvents] = useState<any[]>([]);
1082
+
1083
+ useEffect(() => {
1084
+ streamSystemMetrics(streamSystemMetricsParams);
1085
+ }, [streamSystemMetrics]);
1086
+
1087
+ useEffect(() => {
1088
+ if (isPending(streamSystemMetricsResp)) {
1089
+ // Use flushSync only if you must render every single event (high-frequency streams).
1090
+ flushSync(() => setEvents((xs) => [...xs, streamSystemMetricsResp.data]));
1091
+ }
1092
+ }, [streamSystemMetricsResp]);
1093
+
1094
+ if (isError(streamSystemMetricsResp)) return <>Stream error</>;
1095
+ return (
1096
+ <>
1097
+ {isPending(streamSystemMetricsResp) && (
1098
+ <pre>{JSON.stringify(events, null, 2)}</pre>
1099
+ )}
1100
+ {isSuccess(streamSystemMetricsResp) && (
1101
+ <>Completed ({events.length} events)</>
1102
+ )}
1103
+ </>
1104
+ );
1105
+ }
1106
+ \`\`\`
1107
+
1108
+ ---
1109
+
1110
+ ## Tips, anti-patterns & gotchas
1111
+
1112
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
1113
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
1114
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
1115
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
1116
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
1117
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
1118
+
1119
+ ---
1120
+
1121
+ ## Troubleshooting
1122
+
1123
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
1124
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
1125
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamSystemMetrics\` when appropriate.
1126
+ "
1127
+ `;
1128
+
1129
+ exports[`reactSseHookDocs > snapshot — path params only (no request body) 1`] = `
1130
+ "# Intrig SSE Hooks — Quick Guide
1131
+
1132
+ ## When should I use the SSE hook?
1133
+
1134
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
1135
+ - **You only need a final result** → use the regular **stateful hook**.
1136
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
1137
+
1138
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
1139
+
1140
+ ---
1141
+
1142
+ ## Copy-paste starter (fast lane)
1143
+
1144
+ ### 1) Hook import
1145
+
1146
+ \`\`\`ts
1147
+ import { useStreamUserActivity } from "@intrig/react/users/{userId}/activity/client";
1148
+ \`\`\`
1149
+
1150
+ ### 2) Utility guards
1151
+
1152
+ \`\`\`ts
1153
+ import { isPending, isSuccess, isError } from "@intrig/react";
1154
+ \`\`\`
1155
+
1156
+ ### 3) Hook instance (auto-clear on unmount)
1157
+
1158
+ \`\`\`ts
1159
+ const [streamUserActivityResp, streamUserActivity] = useStreamUserActivity({
1160
+ clearOnUnmount: true,
1161
+ });
1162
+ \`\`\`
1163
+
1164
+ ---
1165
+
1166
+ ## TL;DR (copy–paste)
1167
+
1168
+ \`\`\`tsx
1169
+ import { useStreamUserActivity } from "@intrig/react/users/{userId}/activity/client";
1170
+ import { isPending, isSuccess, isError } from "@intrig/react";
1171
+ import { useEffect, useState } from "react";
1172
+
1173
+ export default function Example() {
1174
+ const [streamUserActivityResp, streamUserActivity] = useStreamUserActivity({
1175
+ clearOnUnmount: true,
1176
+ });
1177
+ const [messages, setMessages] = useState<any[]>([]);
1178
+
1179
+ useEffect(() => {
1180
+ streamUserActivity(streamUserActivityParams); // start stream
1181
+ }, [streamUserActivity]);
1182
+
1183
+ useEffect(() => {
1184
+ // SSE delivers messages while state is Pending
1185
+ if (isPending(streamUserActivityResp)) {
1186
+ setMessages((prev) => [...prev, streamUserActivityResp.data]);
1187
+ }
1188
+ }, [streamUserActivityResp]);
1189
+
1190
+ if (isError(streamUserActivityResp)) return <>An error occurred</>;
1191
+ if (isPending(streamUserActivityResp))
1192
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
1193
+ if (isSuccess(streamUserActivityResp)) return <>Completed</>;
1194
+
1195
+ return null;
1196
+ }
1197
+ \`\`\`
1198
+
1199
+ ### Optional types (if generated by your build)
1200
+
1201
+ \`\`\`ts
1202
+ import type { StreamUserActivityParams } from "@intrig/react/users/{userId}/activity/StreamUserActivity.params";
1203
+ import type { StreamUserActivityResponseBody } from "@intrig/react/users/{userId}/activity/StreamUserActivity.response";
1204
+ \`\`\`
1205
+
1206
+ ---
1207
+
1208
+ ## Hook API
1209
+
1210
+ \`\`\`ts
1211
+ // Signature (shape shown; concrete generics vary per generated hook)
1212
+ declare function useStreamUserActivity(options?: {
1213
+ fetchOnMount?: boolean;
1214
+ clearOnUnmount?: boolean; // recommended for streams
1215
+ key?: string; // isolate multiple subscriptions
1216
+ params?: StreamUserActivityParams;
1217
+ body?: unknown;
1218
+ }): [
1219
+ // While streaming: isPending(state) === true and state.data is the latest event
1220
+ NetworkState<StreamUserActivityResponseBody /* or event payload type */, any>,
1221
+ // Start streaming:
1222
+ (req: { params?: StreamUserActivityParams; body?: unknown }) => void,
1223
+ // Clear/close stream:
1224
+ () => void,
1225
+ ];
1226
+ \`\`\`
1227
+
1228
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamUserActivityResp.data\` **only while** \`isPending(streamUserActivityResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
1229
+
1230
+ ---
1231
+
1232
+ ## Usage patterns
1233
+
1234
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
1235
+
1236
+ \`\`\`tsx
1237
+ const [streamUserActivityResp, streamUserActivity] = useStreamUserActivity({
1238
+ clearOnUnmount: true,
1239
+ });
1240
+
1241
+ useEffect(() => {
1242
+ streamUserActivity(streamUserActivityParams);
1243
+ }, [streamUserActivity]);
1244
+ \`\`\`
1245
+
1246
+ <details><summary>Description</summary>
1247
+ Starts the stream when the component mounts and closes it when the component unmounts.
1248
+ </details>
1249
+
1250
+ ### 2) Collect messages into an array (simple collector)
1251
+
1252
+ \`\`\`tsx
1253
+ const [messages, setMessages] = useState<any[]>([]);
1254
+
1255
+ useEffect(() => {
1256
+ if (isPending(streamUserActivityResp))
1257
+ setMessages((m) => [...m, streamUserActivityResp.data]);
1258
+ }, [streamUserActivityResp]);
1259
+ \`\`\`
1260
+
1261
+ <details><summary>Description</summary>
1262
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
1263
+ </details>
1264
+
1265
+ ### 3) Keep only the latest event (cheap UI)
1266
+
1267
+ \`\`\`tsx
1268
+ const latest = isPending(streamUserActivityResp)
1269
+ ? streamUserActivityResp.data
1270
+ : undefined;
1271
+ \`\`\`
1272
+
1273
+ <details><summary>Description</summary>
1274
+ When you only need the most recent message (progress percentage, status line).
1275
+ </details>
1276
+
1277
+ ### 4) Controlled start/stop (user-triggered)
1278
+
1279
+ \`\`\`tsx
1280
+ const [streamUserActivityResp, streamUserActivity, clearStreamUserActivity] =
1281
+ useStreamUserActivity();
1282
+
1283
+ const start = () => streamUserActivity(streamUserActivityParams);
1284
+ const stop = () => clearStreamUserActivity();
1285
+ \`\`\`
1286
+
1287
+ <details><summary>Description</summary>
1288
+ Expose play/pause UI for long streams or admin tools.
1289
+ </details>
1290
+
1291
+ ---
1292
+
1293
+ ## Full example (with flushSync option)
1294
+
1295
+ \`\`\`tsx
1296
+ import { useStreamUserActivity } from "@intrig/react/users/{userId}/activity/client";
1297
+ import { isPending, isSuccess, isError } from "@intrig/react";
1298
+ import { useEffect, useState } from "react";
1299
+ import { flushSync } from "react-dom";
1300
+
1301
+ function MyComponent() {
1302
+ const [streamUserActivityResp, streamUserActivity] = useStreamUserActivity({
1303
+ clearOnUnmount: true,
1304
+ });
1305
+ const [events, setEvents] = useState<any[]>([]);
1306
+
1307
+ useEffect(() => {
1308
+ streamUserActivity(streamUserActivityParams);
1309
+ }, [streamUserActivity]);
1310
+
1311
+ useEffect(() => {
1312
+ if (isPending(streamUserActivityResp)) {
1313
+ // Use flushSync only if you must render every single event (high-frequency streams).
1314
+ flushSync(() => setEvents((xs) => [...xs, streamUserActivityResp.data]));
1315
+ }
1316
+ }, [streamUserActivityResp]);
1317
+
1318
+ if (isError(streamUserActivityResp)) return <>Stream error</>;
1319
+ return (
1320
+ <>
1321
+ {isPending(streamUserActivityResp) && (
1322
+ <pre>{JSON.stringify(events, null, 2)}</pre>
1323
+ )}
1324
+ {isSuccess(streamUserActivityResp) && (
1325
+ <>Completed ({events.length} events)</>
1326
+ )}
1327
+ </>
1328
+ );
1329
+ }
1330
+ \`\`\`
1331
+
1332
+ ---
1333
+
1334
+ ## Tips, anti-patterns & gotchas
1335
+
1336
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
1337
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
1338
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
1339
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
1340
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
1341
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
1342
+
1343
+ ---
1344
+
1345
+ ## Troubleshooting
1346
+
1347
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
1348
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
1349
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamUserActivity\` when appropriate.
1350
+ "
1351
+ `;
1352
+
1353
+ exports[`reactSseHookDocs > snapshot — request body and path params 1`] = `
1354
+ "# Intrig SSE Hooks — Quick Guide
1355
+
1356
+ ## When should I use the SSE hook?
1357
+
1358
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
1359
+ - **You only need a final result** → use the regular **stateful hook**.
1360
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
1361
+
1362
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
1363
+
1364
+ ---
1365
+
1366
+ ## Copy-paste starter (fast lane)
1367
+
1368
+ ### 1) Hook import
1369
+
1370
+ \`\`\`ts
1371
+ import { useStreamTaskProgress } from "@intrig/react/tasks/{taskId}/progress/client";
1372
+ \`\`\`
1373
+
1374
+ ### 2) Utility guards
1375
+
1376
+ \`\`\`ts
1377
+ import { isPending, isSuccess, isError } from "@intrig/react";
1378
+ \`\`\`
1379
+
1380
+ ### 3) Hook instance (auto-clear on unmount)
1381
+
1382
+ \`\`\`ts
1383
+ const [streamTaskProgressResp, streamTaskProgress] = useStreamTaskProgress({
1384
+ clearOnUnmount: true,
1385
+ });
1386
+ \`\`\`
1387
+
1388
+ ---
1389
+
1390
+ ## TL;DR (copy–paste)
1391
+
1392
+ \`\`\`tsx
1393
+ import { useStreamTaskProgress } from "@intrig/react/tasks/{taskId}/progress/client";
1394
+ import { isPending, isSuccess, isError } from "@intrig/react";
1395
+ import { useEffect, useState } from "react";
1396
+
1397
+ export default function Example() {
1398
+ const [streamTaskProgressResp, streamTaskProgress] = useStreamTaskProgress({
1399
+ clearOnUnmount: true,
1400
+ });
1401
+ const [messages, setMessages] = useState<any[]>([]);
1402
+
1403
+ useEffect(() => {
1404
+ streamTaskProgress(streamTaskProgressRequest, streamTaskProgressParams); // start stream
1405
+ }, [streamTaskProgress]);
1406
+
1407
+ useEffect(() => {
1408
+ // SSE delivers messages while state is Pending
1409
+ if (isPending(streamTaskProgressResp)) {
1410
+ setMessages((prev) => [...prev, streamTaskProgressResp.data]);
1411
+ }
1412
+ }, [streamTaskProgressResp]);
1413
+
1414
+ if (isError(streamTaskProgressResp)) return <>An error occurred</>;
1415
+ if (isPending(streamTaskProgressResp))
1416
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
1417
+ if (isSuccess(streamTaskProgressResp)) return <>Completed</>;
1418
+
1419
+ return null;
1420
+ }
1421
+ \`\`\`
1422
+
1423
+ ### Optional types (if generated by your build)
1424
+
1425
+ \`\`\`ts
1426
+ import type { StreamTaskProgressRequest } from "@intrig/react/demo_api/components/schemas/StreamTaskProgressRequest";
1427
+ import type { StreamTaskProgressParams } from "@intrig/react/tasks/{taskId}/progress/StreamTaskProgress.params";
1428
+ import type { StreamTaskProgressResponseBody } from "@intrig/react/tasks/{taskId}/progress/StreamTaskProgress.response";
1429
+ \`\`\`
1430
+
1431
+ ---
1432
+
1433
+ ## Hook API
1434
+
1435
+ \`\`\`ts
1436
+ // Signature (shape shown; concrete generics vary per generated hook)
1437
+ declare function useStreamTaskProgress(options?: {
1438
+ fetchOnMount?: boolean;
1439
+ clearOnUnmount?: boolean; // recommended for streams
1440
+ key?: string; // isolate multiple subscriptions
1441
+ params?: StreamTaskProgressParams;
1442
+ body?: StreamTaskProgressRequest;
1443
+ }): [
1444
+ // While streaming: isPending(state) === true and state.data is the latest event
1445
+ NetworkState<StreamTaskProgressResponseBody /* or event payload type */, any>,
1446
+ // Start streaming:
1447
+ (req: {
1448
+ params?: StreamTaskProgressParams;
1449
+ body?: StreamTaskProgressRequest;
1450
+ }) => void,
1451
+ // Clear/close stream:
1452
+ () => void,
1453
+ ];
1454
+ \`\`\`
1455
+
1456
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamTaskProgressResp.data\` **only while** \`isPending(streamTaskProgressResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
1457
+
1458
+ ---
1459
+
1460
+ ## Usage patterns
1461
+
1462
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
1463
+
1464
+ \`\`\`tsx
1465
+ const [streamTaskProgressResp, streamTaskProgress] = useStreamTaskProgress({
1466
+ clearOnUnmount: true,
1467
+ });
1468
+
1469
+ useEffect(() => {
1470
+ streamTaskProgress(streamTaskProgressRequest, streamTaskProgressParams);
1471
+ }, [streamTaskProgress]);
1472
+ \`\`\`
1473
+
1474
+ <details><summary>Description</summary>
1475
+ Starts the stream when the component mounts and closes it when the component unmounts.
1476
+ </details>
1477
+
1478
+ ### 2) Collect messages into an array (simple collector)
1479
+
1480
+ \`\`\`tsx
1481
+ const [messages, setMessages] = useState<any[]>([]);
1482
+
1483
+ useEffect(() => {
1484
+ if (isPending(streamTaskProgressResp))
1485
+ setMessages((m) => [...m, streamTaskProgressResp.data]);
1486
+ }, [streamTaskProgressResp]);
1487
+ \`\`\`
1488
+
1489
+ <details><summary>Description</summary>
1490
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
1491
+ </details>
1492
+
1493
+ ### 3) Keep only the latest event (cheap UI)
1494
+
1495
+ \`\`\`tsx
1496
+ const latest = isPending(streamTaskProgressResp)
1497
+ ? streamTaskProgressResp.data
1498
+ : undefined;
1499
+ \`\`\`
1500
+
1501
+ <details><summary>Description</summary>
1502
+ When you only need the most recent message (progress percentage, status line).
1503
+ </details>
1504
+
1505
+ ### 4) Controlled start/stop (user-triggered)
1506
+
1507
+ \`\`\`tsx
1508
+ const [streamTaskProgressResp, streamTaskProgress, clearStreamTaskProgress] =
1509
+ useStreamTaskProgress();
1510
+
1511
+ const start = () =>
1512
+ streamTaskProgress(streamTaskProgressRequest, streamTaskProgressParams);
1513
+ const stop = () => clearStreamTaskProgress();
1514
+ \`\`\`
1515
+
1516
+ <details><summary>Description</summary>
1517
+ Expose play/pause UI for long streams or admin tools.
1518
+ </details>
1519
+
1520
+ ---
1521
+
1522
+ ## Full example (with flushSync option)
1523
+
1524
+ \`\`\`tsx
1525
+ import { useStreamTaskProgress } from "@intrig/react/tasks/{taskId}/progress/client";
1526
+ import { isPending, isSuccess, isError } from "@intrig/react";
1527
+ import { useEffect, useState } from "react";
1528
+ import { flushSync } from "react-dom";
1529
+
1530
+ function MyComponent() {
1531
+ const [streamTaskProgressResp, streamTaskProgress] = useStreamTaskProgress({
1532
+ clearOnUnmount: true,
1533
+ });
1534
+ const [events, setEvents] = useState<any[]>([]);
1535
+
1536
+ useEffect(() => {
1537
+ streamTaskProgress(streamTaskProgressRequest, streamTaskProgressParams);
1538
+ }, [streamTaskProgress]);
1539
+
1540
+ useEffect(() => {
1541
+ if (isPending(streamTaskProgressResp)) {
1542
+ // Use flushSync only if you must render every single event (high-frequency streams).
1543
+ flushSync(() => setEvents((xs) => [...xs, streamTaskProgressResp.data]));
1544
+ }
1545
+ }, [streamTaskProgressResp]);
1546
+
1547
+ if (isError(streamTaskProgressResp)) return <>Stream error</>;
1548
+ return (
1549
+ <>
1550
+ {isPending(streamTaskProgressResp) && (
1551
+ <pre>{JSON.stringify(events, null, 2)}</pre>
1552
+ )}
1553
+ {isSuccess(streamTaskProgressResp) && (
1554
+ <>Completed ({events.length} events)</>
1555
+ )}
1556
+ </>
1557
+ );
1558
+ }
1559
+ \`\`\`
1560
+
1561
+ ---
1562
+
1563
+ ## Tips, anti-patterns & gotchas
1564
+
1565
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
1566
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
1567
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
1568
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
1569
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
1570
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
1571
+
1572
+ ---
1573
+
1574
+ ## Troubleshooting
1575
+
1576
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
1577
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
1578
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamTaskProgress\` when appropriate.
1579
+ "
1580
+ `;
1581
+
1582
+ exports[`reactSseHookDocs > snapshot — request body only (no path params) 1`] = `
1583
+ "# Intrig SSE Hooks — Quick Guide
1584
+
1585
+ ## When should I use the SSE hook?
1586
+
1587
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
1588
+ - **You only need a final result** → use the regular **stateful hook**.
1589
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
1590
+
1591
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
1592
+
1593
+ ---
1594
+
1595
+ ## Copy-paste starter (fast lane)
1596
+
1597
+ ### 1) Hook import
1598
+
1599
+ \`\`\`ts
1600
+ import { useStreamCustomEvents } from "@intrig/react/events/client";
1601
+ \`\`\`
1602
+
1603
+ ### 2) Utility guards
1604
+
1605
+ \`\`\`ts
1606
+ import { isPending, isSuccess, isError } from "@intrig/react";
1607
+ \`\`\`
1608
+
1609
+ ### 3) Hook instance (auto-clear on unmount)
1610
+
1611
+ \`\`\`ts
1612
+ const [streamCustomEventsResp, streamCustomEvents] = useStreamCustomEvents({
1613
+ clearOnUnmount: true,
1614
+ });
1615
+ \`\`\`
1616
+
1617
+ ---
1618
+
1619
+ ## TL;DR (copy–paste)
1620
+
1621
+ \`\`\`tsx
1622
+ import { useStreamCustomEvents } from "@intrig/react/events/client";
1623
+ import { isPending, isSuccess, isError } from "@intrig/react";
1624
+ import { useEffect, useState } from "react";
1625
+
1626
+ export default function Example() {
1627
+ const [streamCustomEventsResp, streamCustomEvents] = useStreamCustomEvents({
1628
+ clearOnUnmount: true,
1629
+ });
1630
+ const [messages, setMessages] = useState<any[]>([]);
1631
+
1632
+ useEffect(() => {
1633
+ streamCustomEvents(streamCustomEventsRequest, {}); // start stream
1634
+ }, [streamCustomEvents]);
1635
+
1636
+ useEffect(() => {
1637
+ // SSE delivers messages while state is Pending
1638
+ if (isPending(streamCustomEventsResp)) {
1639
+ setMessages((prev) => [...prev, streamCustomEventsResp.data]);
1640
+ }
1641
+ }, [streamCustomEventsResp]);
1642
+
1643
+ if (isError(streamCustomEventsResp)) return <>An error occurred</>;
1644
+ if (isPending(streamCustomEventsResp))
1645
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
1646
+ if (isSuccess(streamCustomEventsResp)) return <>Completed</>;
1647
+
1648
+ return null;
1649
+ }
1650
+ \`\`\`
1651
+
1652
+ ### Optional types (if generated by your build)
1653
+
1654
+ \`\`\`ts
1655
+ import type { StreamCustomEventsRequest } from "@intrig/react/demo_api/components/schemas/StreamCustomEventsRequest";
1656
+ import type { StreamCustomEventsResponseBody } from "@intrig/react/events/StreamCustomEvents.response";
1657
+ \`\`\`
1658
+
1659
+ ---
1660
+
1661
+ ## Hook API
1662
+
1663
+ \`\`\`ts
1664
+ // Signature (shape shown; concrete generics vary per generated hook)
1665
+ declare function useStreamCustomEvents(options?: {
1666
+ fetchOnMount?: boolean;
1667
+ clearOnUnmount?: boolean; // recommended for streams
1668
+ key?: string; // isolate multiple subscriptions
1669
+ params?: unknown;
1670
+ body?: StreamCustomEventsRequest;
1671
+ }): [
1672
+ // While streaming: isPending(state) === true and state.data is the latest event
1673
+ NetworkState<StreamCustomEventsResponseBody /* or event payload type */, any>,
1674
+ // Start streaming:
1675
+ (req: { params?: unknown; body?: StreamCustomEventsRequest }) => void,
1676
+ // Clear/close stream:
1677
+ () => void,
1678
+ ];
1679
+ \`\`\`
1680
+
1681
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamCustomEventsResp.data\` **only while** \`isPending(streamCustomEventsResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
1682
+
1683
+ ---
1684
+
1685
+ ## Usage patterns
1686
+
1687
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
1688
+
1689
+ \`\`\`tsx
1690
+ const [streamCustomEventsResp, streamCustomEvents] = useStreamCustomEvents({
1691
+ clearOnUnmount: true,
1692
+ });
1693
+
1694
+ useEffect(() => {
1695
+ streamCustomEvents(streamCustomEventsRequest, {});
1696
+ }, [streamCustomEvents]);
1697
+ \`\`\`
1698
+
1699
+ <details><summary>Description</summary>
1700
+ Starts the stream when the component mounts and closes it when the component unmounts.
1701
+ </details>
1702
+
1703
+ ### 2) Collect messages into an array (simple collector)
1704
+
1705
+ \`\`\`tsx
1706
+ const [messages, setMessages] = useState<any[]>([]);
1707
+
1708
+ useEffect(() => {
1709
+ if (isPending(streamCustomEventsResp))
1710
+ setMessages((m) => [...m, streamCustomEventsResp.data]);
1711
+ }, [streamCustomEventsResp]);
1712
+ \`\`\`
1713
+
1714
+ <details><summary>Description</summary>
1715
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
1716
+ </details>
1717
+
1718
+ ### 3) Keep only the latest event (cheap UI)
1719
+
1720
+ \`\`\`tsx
1721
+ const latest = isPending(streamCustomEventsResp)
1722
+ ? streamCustomEventsResp.data
1723
+ : undefined;
1724
+ \`\`\`
1725
+
1726
+ <details><summary>Description</summary>
1727
+ When you only need the most recent message (progress percentage, status line).
1728
+ </details>
1729
+
1730
+ ### 4) Controlled start/stop (user-triggered)
1731
+
1732
+ \`\`\`tsx
1733
+ const [streamCustomEventsResp, streamCustomEvents, clearStreamCustomEvents] =
1734
+ useStreamCustomEvents();
1735
+
1736
+ const start = () => streamCustomEvents(streamCustomEventsRequest, {});
1737
+ const stop = () => clearStreamCustomEvents();
1738
+ \`\`\`
1739
+
1740
+ <details><summary>Description</summary>
1741
+ Expose play/pause UI for long streams or admin tools.
1742
+ </details>
1743
+
1744
+ ---
1745
+
1746
+ ## Full example (with flushSync option)
1747
+
1748
+ \`\`\`tsx
1749
+ import { useStreamCustomEvents } from "@intrig/react/events/client";
1750
+ import { isPending, isSuccess, isError } from "@intrig/react";
1751
+ import { useEffect, useState } from "react";
1752
+ import { flushSync } from "react-dom";
1753
+
1754
+ function MyComponent() {
1755
+ const [streamCustomEventsResp, streamCustomEvents] = useStreamCustomEvents({
1756
+ clearOnUnmount: true,
1757
+ });
1758
+ const [events, setEvents] = useState<any[]>([]);
1759
+
1760
+ useEffect(() => {
1761
+ streamCustomEvents(streamCustomEventsRequest, {});
1762
+ }, [streamCustomEvents]);
1763
+
1764
+ useEffect(() => {
1765
+ if (isPending(streamCustomEventsResp)) {
1766
+ // Use flushSync only if you must render every single event (high-frequency streams).
1767
+ flushSync(() => setEvents((xs) => [...xs, streamCustomEventsResp.data]));
1768
+ }
1769
+ }, [streamCustomEventsResp]);
1770
+
1771
+ if (isError(streamCustomEventsResp)) return <>Stream error</>;
1772
+ return (
1773
+ <>
1774
+ {isPending(streamCustomEventsResp) && (
1775
+ <pre>{JSON.stringify(events, null, 2)}</pre>
1776
+ )}
1777
+ {isSuccess(streamCustomEventsResp) && (
1778
+ <>Completed ({events.length} events)</>
1779
+ )}
1780
+ </>
1781
+ );
1782
+ }
1783
+ \`\`\`
1784
+
1785
+ ---
1786
+
1787
+ ## Tips, anti-patterns & gotchas
1788
+
1789
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
1790
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
1791
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
1792
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
1793
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
1794
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
1795
+
1796
+ ---
1797
+
1798
+ ## Troubleshooting
1799
+
1800
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
1801
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
1802
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamCustomEvents\` when appropriate.
1803
+ "
1804
+ `;
1805
+
1806
+ exports[`reactSseHookDocs > snapshot — simple REST descriptor (no body, no path params) 1`] = `
1807
+ "# Intrig SSE Hooks — Quick Guide
1808
+
1809
+ ## When should I use the SSE hook?
1810
+
1811
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
1812
+ - **You only need a final result** → use the regular **stateful hook**.
1813
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
1814
+
1815
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
1816
+
1817
+ ---
1818
+
1819
+ ## Copy-paste starter (fast lane)
1820
+
1821
+ ### 1) Hook import
1822
+
1823
+ \`\`\`ts
1824
+ import { useStreamLogs } from "@intrig/react/logs/client";
1825
+ \`\`\`
1826
+
1827
+ ### 2) Utility guards
1828
+
1829
+ \`\`\`ts
1830
+ import { isPending, isSuccess, isError } from "@intrig/react";
1831
+ \`\`\`
1832
+
1833
+ ### 3) Hook instance (auto-clear on unmount)
1834
+
1835
+ \`\`\`ts
1836
+ const [streamLogsResp, streamLogs] = useStreamLogs({ clearOnUnmount: true });
1837
+ \`\`\`
1838
+
1839
+ ---
1840
+
1841
+ ## TL;DR (copy–paste)
1842
+
1843
+ \`\`\`tsx
1844
+ import { useStreamLogs } from "@intrig/react/logs/client";
1845
+ import { isPending, isSuccess, isError } from "@intrig/react";
1846
+ import { useEffect, useState } from "react";
1847
+
1848
+ export default function Example() {
1849
+ const [streamLogsResp, streamLogs] = useStreamLogs({ clearOnUnmount: true });
1850
+ const [messages, setMessages] = useState<any[]>([]);
1851
+
1852
+ useEffect(() => {
1853
+ streamLogs({}); // start stream
1854
+ }, [streamLogs]);
1855
+
1856
+ useEffect(() => {
1857
+ // SSE delivers messages while state is Pending
1858
+ if (isPending(streamLogsResp)) {
1859
+ setMessages((prev) => [...prev, streamLogsResp.data]);
1860
+ }
1861
+ }, [streamLogsResp]);
1862
+
1863
+ if (isError(streamLogsResp)) return <>An error occurred</>;
1864
+ if (isPending(streamLogsResp))
1865
+ return <pre>{JSON.stringify(messages, null, 2)}</pre>;
1866
+ if (isSuccess(streamLogsResp)) return <>Completed</>;
1867
+
1868
+ return null;
1869
+ }
1870
+ \`\`\`
1871
+
1872
+ ---
1873
+
1874
+ ## Hook API
1875
+
1876
+ \`\`\`ts
1877
+ // Signature (shape shown; concrete generics vary per generated hook)
1878
+ declare function useStreamLogs(options?: {
1879
+ fetchOnMount?: boolean;
1880
+ clearOnUnmount?: boolean; // recommended for streams
1881
+ key?: string; // isolate multiple subscriptions
1882
+ params?: unknown;
1883
+ body?: unknown;
1884
+ }): [
1885
+ // While streaming: isPending(state) === true and state.data is the latest event
1886
+ NetworkState<StreamLogsResponseBody /* or event payload type */, any>,
1887
+ // Start streaming:
1888
+ (req: { params?: unknown; body?: unknown }) => void,
1889
+ // Clear/close stream:
1890
+ () => void,
1891
+ ];
1892
+ \`\`\`
1893
+
1894
+ > **Important:** For SSE, **each incoming event** is surfaced as \`streamLogsResp.data\` **only while** \`isPending(streamLogsResp)\` is true. On stream completion the hook flips to \`isSuccess\`.
1895
+
1896
+ ---
1897
+
1898
+ ## Usage patterns
1899
+
1900
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
1901
+
1902
+ \`\`\`tsx
1903
+ const [streamLogsResp, streamLogs] = useStreamLogs({ clearOnUnmount: true });
1904
+
1905
+ useEffect(() => {
1906
+ streamLogs({});
1907
+ }, [streamLogs]);
1908
+ \`\`\`
1909
+
1910
+ <details><summary>Description</summary>
1911
+ Starts the stream when the component mounts and closes it when the component unmounts.
1912
+ </details>
1913
+
1914
+ ### 2) Collect messages into an array (simple collector)
1915
+
1916
+ \`\`\`tsx
1917
+ const [messages, setMessages] = useState<any[]>([]);
1918
+
1919
+ useEffect(() => {
1920
+ if (isPending(streamLogsResp))
1921
+ setMessages((m) => [...m, streamLogsResp.data]);
1922
+ }, [streamLogsResp]);
1923
+ \`\`\`
1924
+
1925
+ <details><summary>Description</summary>
1926
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
1927
+ </details>
1928
+
1929
+ ### 3) Keep only the latest event (cheap UI)
1930
+
1931
+ \`\`\`tsx
1932
+ const latest = isPending(streamLogsResp) ? streamLogsResp.data : undefined;
1933
+ \`\`\`
1934
+
1935
+ <details><summary>Description</summary>
1936
+ When you only need the most recent message (progress percentage, status line).
1937
+ </details>
1938
+
1939
+ ### 4) Controlled start/stop (user-triggered)
1940
+
1941
+ \`\`\`tsx
1942
+ const [streamLogsResp, streamLogs, clearStreamLogs] = useStreamLogs();
1943
+
1944
+ const start = () => streamLogs({});
1945
+ const stop = () => clearStreamLogs();
1946
+ \`\`\`
1947
+
1948
+ <details><summary>Description</summary>
1949
+ Expose play/pause UI for long streams or admin tools.
1950
+ </details>
1951
+
1952
+ ---
1953
+
1954
+ ## Full example (with flushSync option)
1955
+
1956
+ \`\`\`tsx
1957
+ import { useStreamLogs } from "@intrig/react/logs/client";
1958
+ import { isPending, isSuccess, isError } from "@intrig/react";
1959
+ import { useEffect, useState } from "react";
1960
+ import { flushSync } from "react-dom";
1961
+
1962
+ function MyComponent() {
1963
+ const [streamLogsResp, streamLogs] = useStreamLogs({ clearOnUnmount: true });
1964
+ const [events, setEvents] = useState<any[]>([]);
1965
+
1966
+ useEffect(() => {
1967
+ streamLogs({});
1968
+ }, [streamLogs]);
1969
+
1970
+ useEffect(() => {
1971
+ if (isPending(streamLogsResp)) {
1972
+ // Use flushSync only if you must render every single event (high-frequency streams).
1973
+ flushSync(() => setEvents((xs) => [...xs, streamLogsResp.data]));
1974
+ }
1975
+ }, [streamLogsResp]);
1976
+
1977
+ if (isError(streamLogsResp)) return <>Stream error</>;
1978
+ return (
1979
+ <>
1980
+ {isPending(streamLogsResp) && (
1981
+ <pre>{JSON.stringify(events, null, 2)}</pre>
1982
+ )}
1983
+ {isSuccess(streamLogsResp) && <>Completed ({events.length} events)</>}
1984
+ </>
1985
+ );
1986
+ }
1987
+ \`\`\`
1988
+
1989
+ ---
1990
+
1991
+ ## Tips, anti-patterns & gotchas
1992
+
1993
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
1994
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
1995
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
1996
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
1997
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
1998
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
1999
+
2000
+ ---
2001
+
2002
+ ## Troubleshooting
2003
+
2004
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
2005
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
2006
+ - **Stream never completes:** check server end conditions and that you call \`clearStreamLogs\` when appropriate.
2007
+ "
2008
+ `;