@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.
- package/.swcrc +29 -0
- package/README.md +7 -0
- package/eslint.config.mjs +19 -0
- package/package.json +25 -0
- package/project.json +29 -0
- package/rollup.config.cjs +54 -0
- package/rollup.config.mjs +33 -0
- package/src/index.ts +2 -0
- package/src/lib/code-generator.ts +79 -0
- package/src/lib/get-endpoint-documentation.ts +35 -0
- package/src/lib/get-schema-documentation.ts +11 -0
- package/src/lib/internal-types.ts +15 -0
- package/src/lib/plugin-react.ts +22 -0
- package/src/lib/templates/context.template.ts +74 -0
- package/src/lib/templates/docs/__snapshots__/async-hook.spec.ts.snap +889 -0
- package/src/lib/templates/docs/__snapshots__/download-hook.spec.ts.snap +1445 -0
- package/src/lib/templates/docs/__snapshots__/react-hook.spec.ts.snap +1371 -0
- package/src/lib/templates/docs/__snapshots__/sse-hook.spec.ts.snap +2008 -0
- package/src/lib/templates/docs/async-hook.spec.ts +92 -0
- package/src/lib/templates/docs/async-hook.ts +226 -0
- package/src/lib/templates/docs/download-hook.spec.ts +182 -0
- package/src/lib/templates/docs/download-hook.ts +170 -0
- package/src/lib/templates/docs/react-hook.spec.ts +97 -0
- package/src/lib/templates/docs/react-hook.ts +323 -0
- package/src/lib/templates/docs/schema.ts +105 -0
- package/src/lib/templates/docs/sse-hook.spec.ts +207 -0
- package/src/lib/templates/docs/sse-hook.ts +221 -0
- package/src/lib/templates/extra.template.ts +198 -0
- package/src/lib/templates/index.template.ts +14 -0
- package/src/lib/templates/intrigMiddleware.template.ts +21 -0
- package/src/lib/templates/logger.template.ts +67 -0
- package/src/lib/templates/media-type-utils.template.ts +191 -0
- package/src/lib/templates/network-state.template.ts +702 -0
- package/src/lib/templates/packageJson.template.ts +63 -0
- package/src/lib/templates/provider/__tests__/provider-templates.spec.ts +209 -0
- package/src/lib/templates/provider/axios-config.template.ts +49 -0
- package/src/lib/templates/provider/hooks.template.ts +240 -0
- package/src/lib/templates/provider/interfaces.template.ts +72 -0
- package/src/lib/templates/provider/intrig-provider-stub.template.ts +73 -0
- package/src/lib/templates/provider/intrig-provider.template.ts +185 -0
- package/src/lib/templates/provider/main.template.ts +48 -0
- package/src/lib/templates/provider/reducer.template.ts +50 -0
- package/src/lib/templates/provider/status-trap.template.ts +80 -0
- package/src/lib/templates/provider.template.ts +698 -0
- package/src/lib/templates/source/controller/method/asyncFunctionHook.template.ts +196 -0
- package/src/lib/templates/source/controller/method/clientIndex.template.ts +38 -0
- package/src/lib/templates/source/controller/method/download.template.ts +256 -0
- package/src/lib/templates/source/controller/method/params.template.ts +31 -0
- package/src/lib/templates/source/controller/method/requestHook.template.ts +220 -0
- package/src/lib/templates/source/type/typeTemplate.ts +257 -0
- package/src/lib/templates/swcrc.template.ts +25 -0
- package/src/lib/templates/tsconfig.template.ts +37 -0
- package/src/lib/templates/type-utils.template.ts +28 -0
- package/tsconfig.json +13 -0
- 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
|
+
`;
|