tempest-react-sdk 0.61.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/sse/create-event-stream.cjs +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js +32 -27
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/tempest-react-sdk.d.ts +180 -21
- package/dist/utils/dev-mode.cjs.map +1 -1
- package/dist/utils/dev-mode.js.map +1 -1
- package/dist/utils/json-frame.cjs +1 -1
- package/dist/utils/json-frame.cjs.map +1 -1
- package/dist/utils/json-frame.js +33 -14
- package/dist/utils/json-frame.js.map +1 -1
- package/dist/utils/schema-like.cjs +2 -0
- package/dist/utils/schema-like.cjs.map +1 -0
- package/dist/utils/schema-like.js +43 -0
- package/dist/utils/schema-like.js.map +1 -0
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +54 -38
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +5 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -273,8 +273,8 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
273
273
|
| `app` | `AppProviders` (composes `ErrorBoundary` → `QueryProvider` → `ThemeProvider` → `I18nProvider`), type: `AppProvidersProps` |
|
|
274
274
|
| `vite` _(subpath `tempest-react-sdk/vite`)_ | `createViteConfig`, `tempestPwaManifest` (emits `precache-manifest.json` for offline precache), `tempestPwaIcons` (generates the PNG icon set from one SVG via `sharp`), `tempestPwaDevSw` (serves the SW under `npm run dev`), `tempestPwaIcons({ appleSplash })` (Apple splash screens), types: `CreateViteConfigOptions`, `ProxyEntry`, `TempestViteConfig`, `TempestPwaManifestOptions`, `TempestPwaIconsOptions`, `TempestPwaDevSwOptions`, `AppleSplashSpec`, `TempestVitePlugin` |
|
|
275
275
|
| `forms` _(peer: `zod`, `react-hook-form`)_ | `validateForm`, `zodResolver`, `useZodForm`, `validateCPF`, `validateCNPJ`, `formatCEP`, `formatCNPJ`, `unmask`, `CPFInput`, `CNPJInput`, `PhoneInput`, `CEPInput`, `MoneyInput`, `useViaCEP` |
|
|
276
|
-
| `sse` | `createEventStream`, `useEventStream` (`onParseError` para frame que não é JSON)
|
|
277
|
-
| `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE`
|
|
276
|
+
| `sse` | `createEventStream`, `useEventStream` (`schema` valida cada frame — zod/valibot/arktype via Standard Schema — e `onParseError` para frame que não é JSON) |
|
|
277
|
+
| `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `schema` + `onValidationError` (frame fora do contrato não é entregue; o `pong` do heartbeat continua saindo), `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE` |
|
|
278
278
|
| `webrtc` | `tuneOpus` (Opus `fmtp` per audio m-line: per-key merge, `stereo` + `sprop-stereo`, inserts a missing `fmtp`), `setTunedLocalDescription` (falls back to the untouched SDP when the browser refuses the edit), `setSenderBitrate`, `createLinkStatsSampler` / `useLinkStats` / `readRoundTripMs` (throughput as a delta, round trip from the **selected** candidate pair) |
|
|
279
279
|
| `geo` _(opt. peer `leaflet`)_ | `haversineKm`, `pathLengthKm`, `bearingDeg`, `estimateTravel`, `createOSRMBackend`, `boundingBox`, `projectMercator`, `fitProjection`, `createPositionTracker`, `usePositionTracker`, `TrajectoryMap` — tile-free SVG map + trajectory tracking (no external/paid API; Leaflet is an opt-in, lazy-loaded tile layer for self-hosted tiles), types: `Coordinate`, `TrackPoint`, `TravelEstimate`, `TravelMode`, `GeoBounds`, `RoutingBackend` |
|
|
280
280
|
| `br` _(subpath `tempest-react-sdk/br`)_ | `BrazilMap`, `BrazilStateMap`, `BrazilStateCitySelect`, `listStates`, `getState`, `citiesByUf`, `statesByRegion`, `ufChoices`, `cityChoices`, `isValidUf`, `normalizeUf`, `isValidCity`, `loadBrUfGeoJson`, `loadStateMunicipalities`, types `UF`, `BrRegion`, `BrazilState` — clickable 27-UF SVG map + per-state municipality submaps (bundled simplified IBGE GeoJSON, lazy per UF) + states/cities dataset, no external API; mirrors the FastAPI SDK `utils/locations` |
|
|
@@ -308,8 +308,8 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
308
308
|
| `app` | `AppProviders` (composes `ErrorBoundary` → `QueryProvider` → `ThemeProvider` → `I18nProvider`), type: `AppProvidersProps` |
|
|
309
309
|
| `vite` _(subpath `tempest-react-sdk/vite`)_ | `createViteConfig`, `tempestPwaManifest` (emits `precache-manifest.json` for offline precache), `tempestPwaIcons` (generates the PNG icon set from one SVG via `sharp`), `tempestPwaDevSw` (serves the SW under `npm run dev`), `tempestPwaIcons({ appleSplash })` (Apple splash screens), types: `CreateViteConfigOptions`, `ProxyEntry`, `TempestViteConfig`, `TempestPwaManifestOptions`, `TempestPwaIconsOptions`, `TempestPwaDevSwOptions`, `AppleSplashSpec`, `TempestVitePlugin` |
|
|
310
310
|
| `forms` _(peer: `zod`, `react-hook-form`)_ | `validateForm`, `zodResolver`, `useZodForm`, `validateCPF`, `validateCNPJ`, `formatCEP`, `formatCNPJ`, `unmask`, `CPFInput`, `CNPJInput`, `PhoneInput`, `CEPInput`, `MoneyInput`, `useViaCEP` |
|
|
311
|
-
| `sse` | `createEventStream`, `useEventStream` (`onParseError` para frame que não é JSON)
|
|
312
|
-
| `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE`
|
|
311
|
+
| `sse` | `createEventStream`, `useEventStream` (`schema` valida cada frame — zod/valibot/arktype via Standard Schema — e `onParseError` para frame que não é JSON) |
|
|
312
|
+
| `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `schema` + `onValidationError` (frame fora do contrato não é entregue; o `pong` do heartbeat continua saindo), `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE` |
|
|
313
313
|
| `webrtc` | `tuneOpus` (Opus `fmtp` per audio m-line: per-key merge, `stereo` + `sprop-stereo`, inserts a missing `fmtp`), `setTunedLocalDescription` (falls back to the untouched SDP when the browser refuses the edit), `setSenderBitrate`, `createLinkStatsSampler` / `useLinkStats` / `readRoundTripMs` (throughput as a delta, round trip from the **selected** candidate pair) |
|
|
314
314
|
| `geo` _(opt. peer `leaflet`)_ | `haversineKm`, `pathLengthKm`, `bearingDeg`, `estimateTravel`, `createOSRMBackend`, `boundingBox`, `projectMercator`, `fitProjection`, `createPositionTracker`, `usePositionTracker`, `TrajectoryMap` — tile-free SVG map + trajectory tracking (no external/paid API; Leaflet is an opt-in, lazy-loaded tile layer for self-hosted tiles), types: `Coordinate`, `TrackPoint`, `TravelEstimate`, `TravelMode`, `GeoBounds`, `RoutingBackend` |
|
|
315
315
|
| `br` _(subpath `tempest-react-sdk/br`)_ | `BrazilMap`, `BrazilStateMap`, `BrazilStateCitySelect`, `listStates`, `getState`, `citiesByUf`, `statesByRegion`, `ufChoices`, `cityChoices`, `isValidUf`, `normalizeUf`, `isValidCity`, `loadBrUfGeoJson`, `loadStateMunicipalities`, types `UF`, `BrRegion`, `BrazilState` — clickable 27-UF SVG map + per-state municipality submaps (bundled simplified IBGE GeoJSON, lazy per UF) + states/cities dataset, no external API; mirrors the FastAPI SDK `utils/locations` |
|
|
@@ -654,15 +654,17 @@ export async function getUser(id: string) {
|
|
|
654
654
|
|
|
655
655
|
On validation failure `parseResponse` throws an `Error` whose `message` includes the request label and the zod issues — exactly the diagnostic you want during a wire-protocol drift.
|
|
656
656
|
|
|
657
|
-
**Under Vite
|
|
657
|
+
**Under Vite the report turns itself on.** The SDK detects a dev build by reading `process.env.NODE_ENV`, and every supported bundler substitutes that expression while building your app — Vite included, measured on 5.4.21, 6.4.3, 7.3.6 and 8.2.2, in `vite dev` and in `vite build`, with the SDK installed both as a packed tarball and as a `file:` link. (This paragraph said the opposite until 0.62.0; the belief survived because the expression is not substituted in a browser console, which is where it is natural to check.)
|
|
658
|
+
|
|
659
|
+
`setDevBuild` remains for what nothing compiles or nothing configures — a raw service-worker script, a plain `<script type="module">`, or a staging build that never sets `NODE_ENV=production` and would otherwise embed the raw response body in an error a real user sees:
|
|
658
660
|
|
|
659
661
|
```ts
|
|
660
662
|
import { setDevBuild } from "tempest-react-sdk";
|
|
661
663
|
|
|
662
|
-
setDevBuild(
|
|
664
|
+
setDevBuild(false); // staging talking to real data
|
|
663
665
|
```
|
|
664
666
|
|
|
665
|
-
The SDK cannot read `import.meta.env.DEV` for you: Vite would replace it while building _this package_ and the published artifact would carry the constant.
|
|
667
|
+
The SDK cannot read `import.meta.env.DEV` for you: Vite would replace it while building _this package_ and the published artifact would carry the constant.
|
|
666
668
|
|
|
667
669
|
### Upload with progress recipe
|
|
668
670
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../utils/json-frame.cjs");function t(t,n={}){let{withCredentials:r=!1,namedEvents:i=[],heartbeatEvents:a=[`ping`],maxRetries:o=10,initialBackoff:s=1e3,maxBackoff:c=3e4,parser:l,onOpen:u,onMessage:d,onError:f,onParseError:p,onStatusChange:
|
|
1
|
+
const e=require("../utils/json-frame.cjs");function t(t,n={}){let{withCredentials:r=!1,namedEvents:i=[],heartbeatEvents:a=[`ping`],maxRetries:o=10,initialBackoff:s=1e3,maxBackoff:c=3e4,parser:l,onOpen:u,onMessage:d,onError:f,onParseError:p,schema:m,onValidationError:h,onStatusChange:g}=n,_=null,v=null,y=0,b=`idle`,x=!1;function S(e){b!==e&&(b=e,g?.(e))}function C(t,n){if(a.includes(t))return;let r=e.decodeFrame(typeof n.data==`string`?n.data:``,`createEventStream`,{parser:l,onParseError:p,schema:m,onValidationError:h});r.delivered&&d?.({event:t,data:r.data,id:n.lastEventId||void 0,raw:n})}function w(){if(x)return;if(y>=o){S(`error`);return}let e=Math.min(s*2**y,c);y+=1,v=setTimeout(T,e)}function T(){if(x)return;_&&_.close(),S(`connecting`);let e=new EventSource(t,{withCredentials:r});_=e,e.onopen=()=>{y=0,S(`open`),u?.()},e.onmessage=e=>C(`message`,e);for(let t of i)e.addEventListener(t,e=>C(t,e));for(let t of a)e.addEventListener(t,()=>{});e.onerror=t=>{f?.(t),e.close(),_=null,S(`closed`),w()}}function E(){x=!0,v&&=(clearTimeout(v),null),y=0,_&&=(_.close(),null),S(`closed`)}function D(){v&&=(clearTimeout(v),null),y=0,x=!1,T()}return T(),{close:E,reconnect:D,get status(){return b}}}exports.createEventStream=t;
|
|
2
2
|
//# sourceMappingURL=create-event-stream.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-event-stream.cjs","names":[],"sources":["../../src/sse/create-event-stream.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body is the SSE line protocol — accumulate\n * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —\n * plus the reconnect that uses the id it just tracked.\n */\nimport { decodeFrame } from \"../utils/json-frame\";\nexport type EventStreamStatus = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport interface EventStreamMessage<T> {\n /** Server-named event (default `\"message\"`). */\n event: string;\n /** Parsed payload — JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** Server-supplied id, if any. */\n id?: string;\n /** Raw `MessageEvent` for advanced cases. */\n raw: MessageEvent;\n}\n\nexport interface CreateEventStreamOptions<T> {\n /** Send cookies with the EventSource handshake. Default: false. */\n withCredentials?: boolean;\n /** Subscribe to named events in addition to `message`. */\n namedEvents?: readonly string[];\n /** Treat these named events as heartbeat-only (no callback). Default: `[\"ping\"]`. */\n heartbeatEvents?: readonly string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable reconnect. */\n maxRetries?: number;\n /** Initial backoff in ms; doubles per attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff in ms. Default: 30000. */\n maxBackoff?: number;\n /** Parse `event.data`. Defaults to JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n onOpen?: () => void;\n onMessage?: (message: EventStreamMessage<T>) => void;\n onError?: (error: Event) => void;\n onStatusChange?: (status: EventStreamStatus) => void;\n}\n\nexport interface EventStreamController {\n close: () => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: EventStreamStatus;\n}\n\n/**\n * Open a Server-Sent Events stream with automatic exponential-backoff reconnect.\n *\n * Heartbeat events (default `\"ping\"`) keep the socket alive without firing\n * `onMessage`. Pass `withCredentials: true` when the backend authenticates via\n * cookies. Call `close()` from the returned controller to tear down.\n *\n * @param url - Full SSE endpoint URL.\n * @param options - Stream configuration and callbacks.\n * @returns A controller exposing `close`, `reconnect` and the current `status`.\n */\nexport function createEventStream<T = unknown>(\n url: string,\n options: CreateEventStreamOptions<T> = {},\n): EventStreamController {\n const {\n withCredentials = false,\n namedEvents = [],\n heartbeatEvents = [\"ping\"],\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n parser,\n onOpen,\n onMessage,\n onError,\n onParseError,\n onStatusChange,\n } = options;\n\n let source: EventSource | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retries = 0;\n let status: EventStreamStatus = \"idle\";\n let closed = false;\n\n function setStatus(next: EventStreamStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function emit(eventName: string, event: MessageEvent): void {\n if (heartbeatEvents.includes(eventName)) return;\n const decoded = decodeFrame<T>(\n typeof event.data === \"string\" ? event.data : \"\",\n
|
|
1
|
+
{"version":3,"file":"create-event-stream.cjs","names":[],"sources":["../../src/sse/create-event-stream.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body is the SSE line protocol — accumulate\n * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —\n * plus the reconnect that uses the id it just tracked.\n */\nimport { decodeFrame } from \"../utils/json-frame\";\nimport type { SchemaIssue, SchemaLike } from \"../utils/schema-like\";\nexport type EventStreamStatus = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport interface EventStreamMessage<T> {\n /** Server-named event (default `\"message\"`). */\n event: string;\n /** Parsed payload — validated when `schema` is set, JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** Server-supplied id, if any. */\n id?: string;\n /** Raw `MessageEvent` for advanced cases. */\n raw: MessageEvent;\n}\n\nexport interface CreateEventStreamOptions<T> {\n /** Send cookies with the EventSource handshake. Default: false. */\n withCredentials?: boolean;\n /** Subscribe to named events in addition to `message`. */\n namedEvents?: readonly string[];\n /** Treat these named events as heartbeat-only (no callback). Default: `[\"ping\"]`. */\n heartbeatEvents?: readonly string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable reconnect. */\n maxRetries?: number;\n /** Initial backoff in ms; doubles per attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff in ms. Default: 30000. */\n maxBackoff?: number;\n /** Parse `event.data`. Defaults to JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n /**\n * Schema every decoded frame must satisfy, from zod, valibot, arktype or\n * anything else exposing `~standard` or `.safeParse`.\n *\n * Without it nothing changes: the payload reaches `onMessage` announced as\n * `T` on the strength of the type argument alone, which is a promise about\n * the server that TypeScript cannot keep. With it, a frame that does not\n * match is **not** delivered — the same rule `onParseError` already follows —\n * and `onValidationError` hears why. The value delivered is the schema's\n * output, so coercions and defaults are honoured.\n *\n * When `parser` is also supplied, it decodes first and the schema validates\n * what it returned.\n *\n * The validation must be synchronous. A frame is decoded inside the\n * `message` handler and delivered from it, so an async schema would deliver\n * frames in whatever order their validations settled; that case is reported\n * through `onValidationError` instead of awaited.\n */\n schema?: SchemaLike<T>;\n /**\n * A frame was decoded but the `schema` refused it, so it was dropped.\n *\n * The one signal that does not depend on how the app's bundler resolves\n * `process`: the one-time development warning behind `onParseError` needs\n * `isDevBuild()` to be able to answer, and this callback is the app's own.\n */\n onValidationError?: (issues: SchemaIssue[], raw: string) => void;\n onOpen?: () => void;\n onMessage?: (message: EventStreamMessage<T>) => void;\n onError?: (error: Event) => void;\n onStatusChange?: (status: EventStreamStatus) => void;\n}\n\nexport interface EventStreamController {\n close: () => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: EventStreamStatus;\n}\n\n/**\n * Open a Server-Sent Events stream with automatic exponential-backoff reconnect.\n *\n * Heartbeat events (default `\"ping\"`) keep the socket alive without firing\n * `onMessage`. Pass `withCredentials: true` when the backend authenticates via\n * cookies. Call `close()` from the returned controller to tear down.\n *\n * @param url - Full SSE endpoint URL.\n * @param options - Stream configuration and callbacks.\n * @returns A controller exposing `close`, `reconnect` and the current `status`.\n */\nexport function createEventStream<T = unknown>(\n url: string,\n options: CreateEventStreamOptions<T> = {},\n): EventStreamController {\n const {\n withCredentials = false,\n namedEvents = [],\n heartbeatEvents = [\"ping\"],\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n parser,\n onOpen,\n onMessage,\n onError,\n onParseError,\n schema,\n onValidationError,\n onStatusChange,\n } = options;\n\n let source: EventSource | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retries = 0;\n let status: EventStreamStatus = \"idle\";\n let closed = false;\n\n function setStatus(next: EventStreamStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function emit(eventName: string, event: MessageEvent): void {\n if (heartbeatEvents.includes(eventName)) return;\n const decoded = decodeFrame<T>(\n typeof event.data === \"string\" ? event.data : \"\",\n \"createEventStream\",\n { parser, onParseError, schema, onValidationError },\n );\n if (!decoded.delivered) return;\n onMessage?.({\n event: eventName,\n data: decoded.data,\n id: event.lastEventId || undefined,\n raw: event,\n });\n }\n\n function scheduleReconnect(): void {\n if (closed) return;\n if (retries >= maxRetries) {\n setStatus(\"error\");\n return;\n }\n const delay = Math.min(initialBackoff * 2 ** retries, maxBackoff);\n retries += 1;\n retryTimer = setTimeout(connect, delay);\n }\n\n function connect(): void {\n if (closed) return;\n if (source) source.close();\n setStatus(\"connecting\");\n\n const es = new EventSource(url, { withCredentials });\n source = es;\n\n es.onopen = () => {\n retries = 0;\n setStatus(\"open\");\n onOpen?.();\n };\n\n es.onmessage = (event) => emit(\"message\", event);\n for (const name of namedEvents) {\n es.addEventListener(name, (event) => emit(name, event as MessageEvent));\n }\n for (const name of heartbeatEvents) {\n es.addEventListener(name, () => {\n /* heartbeat — keep socket alive */\n });\n }\n\n es.onerror = (event) => {\n onError?.(event);\n es.close();\n source = null;\n setStatus(\"closed\");\n scheduleReconnect();\n };\n }\n\n function close(): void {\n closed = true;\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n if (source) {\n source.close();\n source = null;\n }\n setStatus(\"closed\");\n }\n\n function reconnect(): void {\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n closed = false;\n connect();\n }\n\n connect();\n\n return {\n close,\n reconnect,\n get status() {\n return status;\n },\n };\n}\n"],"mappings":"2CAkGA,SAAgB,EACZ,EACA,EAAuC,CAAC,EACnB,CACrB,GAAM,CACF,kBAAkB,GAClB,cAAc,CAAC,EACf,kBAAkB,CAAC,MAAM,EACzB,aAAa,GACb,iBAAiB,IACjB,aAAa,IACb,SACA,SACA,YACA,UACA,eACA,SACA,oBACA,kBACA,EAEA,EAA6B,KAC7B,EAAmD,KACnD,EAAU,EACV,EAA4B,OAC5B,EAAS,GAEb,SAAS,EAAU,EAA+B,CAC1C,IAAW,IACf,EAAS,EACT,IAAiB,CAAI,EACzB,CAEA,SAAS,EAAK,EAAmB,EAA2B,CACxD,GAAI,EAAgB,SAAS,CAAS,EAAG,OACzC,IAAM,EAAU,EAAA,YACZ,OAAO,EAAM,MAAS,SAAW,EAAM,KAAO,GAC9C,oBACA,CAAE,SAAQ,eAAc,SAAQ,mBAAkB,CACtD,EACK,EAAQ,WACb,IAAY,CACR,MAAO,EACP,KAAM,EAAQ,KACd,GAAI,EAAM,aAAe,IAAA,GACzB,IAAK,CACT,CAAC,CACL,CAEA,SAAS,GAA0B,CAC/B,GAAI,EAAQ,OACZ,GAAI,GAAW,EAAY,CACvB,EAAU,OAAO,EACjB,MACJ,CACA,IAAM,EAAQ,KAAK,IAAI,EAAiB,GAAK,EAAS,CAAU,EAChE,GAAW,EACX,EAAa,WAAW,EAAS,CAAK,CAC1C,CAEA,SAAS,GAAgB,CACrB,GAAI,EAAQ,OACR,GAAQ,EAAO,MAAM,EACzB,EAAU,YAAY,EAEtB,IAAM,EAAK,IAAI,YAAY,EAAK,CAAE,iBAAgB,CAAC,EACnD,EAAS,EAET,EAAG,WAAe,CACd,EAAU,EACV,EAAU,MAAM,EAChB,IAAS,CACb,EAEA,EAAG,UAAa,GAAU,EAAK,UAAW,CAAK,EAC/C,IAAK,IAAM,KAAQ,EACf,EAAG,iBAAiB,EAAO,GAAU,EAAK,EAAM,CAAqB,CAAC,EAE1E,IAAK,IAAM,KAAQ,EACf,EAAG,iBAAiB,MAAY,CAEhC,CAAC,EAGL,EAAG,QAAW,GAAU,CACpB,IAAU,CAAK,EACf,EAAG,MAAM,EACT,EAAS,KACT,EAAU,QAAQ,EAClB,EAAkB,CACtB,CACJ,CAEA,SAAS,GAAc,CACnB,EAAS,GACT,AAEI,KADA,aAAa,CAAU,EACV,MAEjB,EAAU,EACV,AAEI,KADA,EAAO,MAAM,EACJ,MAEb,EAAU,QAAQ,CACtB,CAEA,SAAS,GAAkB,CACvB,AAEI,KADA,aAAa,CAAU,EACV,MAEjB,EAAU,EACV,EAAS,GACT,EAAQ,CACZ,CAIA,OAFA,EAAQ,EAED,CACH,QACA,YACA,IAAI,QAAS,CACT,OAAO,CACX,CACJ,CACJ"}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { decodeFrame as e } from "../utils/json-frame.js";
|
|
2
2
|
//#region src/sse/create-event-stream.ts
|
|
3
3
|
function t(t, n = {}) {
|
|
4
|
-
let { withCredentials: r = !1, namedEvents: i = [], heartbeatEvents: a = ["ping"], maxRetries: o = 10, initialBackoff: s = 1e3, maxBackoff: c = 3e4, parser: l, onOpen: u, onMessage: d, onError: f, onParseError: p,
|
|
5
|
-
function
|
|
6
|
-
|
|
4
|
+
let { withCredentials: r = !1, namedEvents: i = [], heartbeatEvents: a = ["ping"], maxRetries: o = 10, initialBackoff: s = 1e3, maxBackoff: c = 3e4, parser: l, onOpen: u, onMessage: d, onError: f, onParseError: p, schema: m, onValidationError: h, onStatusChange: g } = n, _ = null, v = null, y = 0, b = "idle", x = !1;
|
|
5
|
+
function S(e) {
|
|
6
|
+
b !== e && (b = e, g?.(e));
|
|
7
7
|
}
|
|
8
|
-
function
|
|
8
|
+
function C(t, n) {
|
|
9
9
|
if (a.includes(t)) return;
|
|
10
|
-
let r = e(typeof n.data == "string" ? n.data : "",
|
|
10
|
+
let r = e(typeof n.data == "string" ? n.data : "", "createEventStream", {
|
|
11
|
+
parser: l,
|
|
12
|
+
onParseError: p,
|
|
13
|
+
schema: m,
|
|
14
|
+
onValidationError: h
|
|
15
|
+
});
|
|
11
16
|
r.delivered && d?.({
|
|
12
17
|
event: t,
|
|
13
18
|
data: r.data,
|
|
@@ -15,39 +20,39 @@ function t(t, n = {}) {
|
|
|
15
20
|
raw: n
|
|
16
21
|
});
|
|
17
22
|
}
|
|
18
|
-
function
|
|
19
|
-
if (
|
|
20
|
-
if (
|
|
21
|
-
|
|
23
|
+
function w() {
|
|
24
|
+
if (x) return;
|
|
25
|
+
if (y >= o) {
|
|
26
|
+
S("error");
|
|
22
27
|
return;
|
|
23
28
|
}
|
|
24
|
-
let e = Math.min(s * 2 **
|
|
25
|
-
|
|
29
|
+
let e = Math.min(s * 2 ** y, c);
|
|
30
|
+
y += 1, v = setTimeout(T, e);
|
|
26
31
|
}
|
|
27
|
-
function
|
|
28
|
-
if (
|
|
29
|
-
|
|
32
|
+
function T() {
|
|
33
|
+
if (x) return;
|
|
34
|
+
_ && _.close(), S("connecting");
|
|
30
35
|
let e = new EventSource(t, { withCredentials: r });
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}, e.onmessage = (e) =>
|
|
34
|
-
for (let t of i) e.addEventListener(t, (e) =>
|
|
36
|
+
_ = e, e.onopen = () => {
|
|
37
|
+
y = 0, S("open"), u?.();
|
|
38
|
+
}, e.onmessage = (e) => C("message", e);
|
|
39
|
+
for (let t of i) e.addEventListener(t, (e) => C(t, e));
|
|
35
40
|
for (let t of a) e.addEventListener(t, () => {});
|
|
36
41
|
e.onerror = (t) => {
|
|
37
|
-
f?.(t), e.close(),
|
|
42
|
+
f?.(t), e.close(), _ = null, S("closed"), w();
|
|
38
43
|
};
|
|
39
44
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
45
|
+
function E() {
|
|
46
|
+
x = !0, v &&= (clearTimeout(v), null), y = 0, _ &&= (_.close(), null), S("closed");
|
|
42
47
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
48
|
+
function D() {
|
|
49
|
+
v &&= (clearTimeout(v), null), y = 0, x = !1, T();
|
|
45
50
|
}
|
|
46
|
-
return
|
|
47
|
-
close:
|
|
48
|
-
reconnect:
|
|
51
|
+
return T(), {
|
|
52
|
+
close: E,
|
|
53
|
+
reconnect: D,
|
|
49
54
|
get status() {
|
|
50
|
-
return
|
|
55
|
+
return b;
|
|
51
56
|
}
|
|
52
57
|
};
|
|
53
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-event-stream.js","names":[],"sources":["../../src/sse/create-event-stream.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body is the SSE line protocol — accumulate\n * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —\n * plus the reconnect that uses the id it just tracked.\n */\nimport { decodeFrame } from \"../utils/json-frame\";\nexport type EventStreamStatus = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport interface EventStreamMessage<T> {\n /** Server-named event (default `\"message\"`). */\n event: string;\n /** Parsed payload — JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** Server-supplied id, if any. */\n id?: string;\n /** Raw `MessageEvent` for advanced cases. */\n raw: MessageEvent;\n}\n\nexport interface CreateEventStreamOptions<T> {\n /** Send cookies with the EventSource handshake. Default: false. */\n withCredentials?: boolean;\n /** Subscribe to named events in addition to `message`. */\n namedEvents?: readonly string[];\n /** Treat these named events as heartbeat-only (no callback). Default: `[\"ping\"]`. */\n heartbeatEvents?: readonly string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable reconnect. */\n maxRetries?: number;\n /** Initial backoff in ms; doubles per attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff in ms. Default: 30000. */\n maxBackoff?: number;\n /** Parse `event.data`. Defaults to JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n onOpen?: () => void;\n onMessage?: (message: EventStreamMessage<T>) => void;\n onError?: (error: Event) => void;\n onStatusChange?: (status: EventStreamStatus) => void;\n}\n\nexport interface EventStreamController {\n close: () => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: EventStreamStatus;\n}\n\n/**\n * Open a Server-Sent Events stream with automatic exponential-backoff reconnect.\n *\n * Heartbeat events (default `\"ping\"`) keep the socket alive without firing\n * `onMessage`. Pass `withCredentials: true` when the backend authenticates via\n * cookies. Call `close()` from the returned controller to tear down.\n *\n * @param url - Full SSE endpoint URL.\n * @param options - Stream configuration and callbacks.\n * @returns A controller exposing `close`, `reconnect` and the current `status`.\n */\nexport function createEventStream<T = unknown>(\n url: string,\n options: CreateEventStreamOptions<T> = {},\n): EventStreamController {\n const {\n withCredentials = false,\n namedEvents = [],\n heartbeatEvents = [\"ping\"],\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n parser,\n onOpen,\n onMessage,\n onError,\n onParseError,\n onStatusChange,\n } = options;\n\n let source: EventSource | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retries = 0;\n let status: EventStreamStatus = \"idle\";\n let closed = false;\n\n function setStatus(next: EventStreamStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function emit(eventName: string, event: MessageEvent): void {\n if (heartbeatEvents.includes(eventName)) return;\n const decoded = decodeFrame<T>(\n typeof event.data === \"string\" ? event.data : \"\",\n
|
|
1
|
+
{"version":3,"file":"create-event-stream.js","names":[],"sources":["../../src/sse/create-event-stream.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body is the SSE line protocol — accumulate\n * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —\n * plus the reconnect that uses the id it just tracked.\n */\nimport { decodeFrame } from \"../utils/json-frame\";\nimport type { SchemaIssue, SchemaLike } from \"../utils/schema-like\";\nexport type EventStreamStatus = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport interface EventStreamMessage<T> {\n /** Server-named event (default `\"message\"`). */\n event: string;\n /** Parsed payload — validated when `schema` is set, JSON-decoded when possible, raw string otherwise. */\n data: T;\n /** Server-supplied id, if any. */\n id?: string;\n /** Raw `MessageEvent` for advanced cases. */\n raw: MessageEvent;\n}\n\nexport interface CreateEventStreamOptions<T> {\n /** Send cookies with the EventSource handshake. Default: false. */\n withCredentials?: boolean;\n /** Subscribe to named events in addition to `message`. */\n namedEvents?: readonly string[];\n /** Treat these named events as heartbeat-only (no callback). Default: `[\"ping\"]`. */\n heartbeatEvents?: readonly string[];\n /** Max reconnect attempts. Default: 10. Pass 0 to disable reconnect. */\n maxRetries?: number;\n /** Initial backoff in ms; doubles per attempt, capped at `maxBackoff`. Default: 1000. */\n initialBackoff?: number;\n /** Maximum backoff in ms. Default: 30000. */\n maxBackoff?: number;\n /** Parse `event.data`. Defaults to JSON with raw-string fallback. */\n parser?: (raw: string) => T;\n /**\n * A frame arrived that is not valid JSON, and no `parser` was supplied.\n *\n * Registering this drops the frame instead of delivering it: the previous\n * behaviour handed `onMessage` the raw `string` announced as your message\n * type, so the failure surfaced later, at the first property access, with\n * nothing left pointing at the parse. Leave it out and that behaviour is\n * kept, with a one-time warning in development builds.\n */\n onParseError?: (error: unknown, raw: string) => void;\n /**\n * Schema every decoded frame must satisfy, from zod, valibot, arktype or\n * anything else exposing `~standard` or `.safeParse`.\n *\n * Without it nothing changes: the payload reaches `onMessage` announced as\n * `T` on the strength of the type argument alone, which is a promise about\n * the server that TypeScript cannot keep. With it, a frame that does not\n * match is **not** delivered — the same rule `onParseError` already follows —\n * and `onValidationError` hears why. The value delivered is the schema's\n * output, so coercions and defaults are honoured.\n *\n * When `parser` is also supplied, it decodes first and the schema validates\n * what it returned.\n *\n * The validation must be synchronous. A frame is decoded inside the\n * `message` handler and delivered from it, so an async schema would deliver\n * frames in whatever order their validations settled; that case is reported\n * through `onValidationError` instead of awaited.\n */\n schema?: SchemaLike<T>;\n /**\n * A frame was decoded but the `schema` refused it, so it was dropped.\n *\n * The one signal that does not depend on how the app's bundler resolves\n * `process`: the one-time development warning behind `onParseError` needs\n * `isDevBuild()` to be able to answer, and this callback is the app's own.\n */\n onValidationError?: (issues: SchemaIssue[], raw: string) => void;\n onOpen?: () => void;\n onMessage?: (message: EventStreamMessage<T>) => void;\n onError?: (error: Event) => void;\n onStatusChange?: (status: EventStreamStatus) => void;\n}\n\nexport interface EventStreamController {\n close: () => void;\n /** Force an immediate reconnect, resetting the retry counter. */\n reconnect: () => void;\n /** Current connection status. */\n readonly status: EventStreamStatus;\n}\n\n/**\n * Open a Server-Sent Events stream with automatic exponential-backoff reconnect.\n *\n * Heartbeat events (default `\"ping\"`) keep the socket alive without firing\n * `onMessage`. Pass `withCredentials: true` when the backend authenticates via\n * cookies. Call `close()` from the returned controller to tear down.\n *\n * @param url - Full SSE endpoint URL.\n * @param options - Stream configuration and callbacks.\n * @returns A controller exposing `close`, `reconnect` and the current `status`.\n */\nexport function createEventStream<T = unknown>(\n url: string,\n options: CreateEventStreamOptions<T> = {},\n): EventStreamController {\n const {\n withCredentials = false,\n namedEvents = [],\n heartbeatEvents = [\"ping\"],\n maxRetries = 10,\n initialBackoff = 1000,\n maxBackoff = 30000,\n parser,\n onOpen,\n onMessage,\n onError,\n onParseError,\n schema,\n onValidationError,\n onStatusChange,\n } = options;\n\n let source: EventSource | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retries = 0;\n let status: EventStreamStatus = \"idle\";\n let closed = false;\n\n function setStatus(next: EventStreamStatus): void {\n if (status === next) return;\n status = next;\n onStatusChange?.(next);\n }\n\n function emit(eventName: string, event: MessageEvent): void {\n if (heartbeatEvents.includes(eventName)) return;\n const decoded = decodeFrame<T>(\n typeof event.data === \"string\" ? event.data : \"\",\n \"createEventStream\",\n { parser, onParseError, schema, onValidationError },\n );\n if (!decoded.delivered) return;\n onMessage?.({\n event: eventName,\n data: decoded.data,\n id: event.lastEventId || undefined,\n raw: event,\n });\n }\n\n function scheduleReconnect(): void {\n if (closed) return;\n if (retries >= maxRetries) {\n setStatus(\"error\");\n return;\n }\n const delay = Math.min(initialBackoff * 2 ** retries, maxBackoff);\n retries += 1;\n retryTimer = setTimeout(connect, delay);\n }\n\n function connect(): void {\n if (closed) return;\n if (source) source.close();\n setStatus(\"connecting\");\n\n const es = new EventSource(url, { withCredentials });\n source = es;\n\n es.onopen = () => {\n retries = 0;\n setStatus(\"open\");\n onOpen?.();\n };\n\n es.onmessage = (event) => emit(\"message\", event);\n for (const name of namedEvents) {\n es.addEventListener(name, (event) => emit(name, event as MessageEvent));\n }\n for (const name of heartbeatEvents) {\n es.addEventListener(name, () => {\n /* heartbeat — keep socket alive */\n });\n }\n\n es.onerror = (event) => {\n onError?.(event);\n es.close();\n source = null;\n setStatus(\"closed\");\n scheduleReconnect();\n };\n }\n\n function close(): void {\n closed = true;\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n if (source) {\n source.close();\n source = null;\n }\n setStatus(\"closed\");\n }\n\n function reconnect(): void {\n if (retryTimer) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n retries = 0;\n closed = false;\n connect();\n }\n\n connect();\n\n return {\n close,\n reconnect,\n get status() {\n return status;\n },\n };\n}\n"],"mappings":";;AAkGA,SAAgB,EACZ,GACA,IAAuC,CAAC,GACnB;CACrB,IAAM,EACF,qBAAkB,IAClB,iBAAc,CAAC,GACf,qBAAkB,CAAC,MAAM,GACzB,gBAAa,IACb,oBAAiB,KACjB,gBAAa,KACb,WACA,WACA,cACA,YACA,iBACA,WACA,sBACA,sBACA,GAEA,IAA6B,MAC7B,IAAmD,MACnD,IAAU,GACV,IAA4B,QAC5B,IAAS;CAEb,SAAS,EAAU,GAA+B;EAC1C,MAAW,MACf,IAAS,GACT,IAAiB,CAAI;CACzB;CAEA,SAAS,EAAK,GAAmB,GAA2B;EACxD,IAAI,EAAgB,SAAS,CAAS,GAAG;EACzC,IAAM,IAAU,EACZ,OAAO,EAAM,QAAS,WAAW,EAAM,OAAO,IAC9C,qBACA;GAAE;GAAQ;GAAc;GAAQ;EAAkB,CACtD;EACK,EAAQ,aACb,IAAY;GACR,OAAO;GACP,MAAM,EAAQ;GACd,IAAI,EAAM,eAAe,KAAA;GACzB,KAAK;EACT,CAAC;CACL;CAEA,SAAS,IAA0B;EAC/B,IAAI,GAAQ;EACZ,IAAI,KAAW,GAAY;GACvB,EAAU,OAAO;GACjB;EACJ;EACA,IAAM,IAAQ,KAAK,IAAI,IAAiB,KAAK,GAAS,CAAU;EAEhE,AADA,KAAW,GACX,IAAa,WAAW,GAAS,CAAK;CAC1C;CAEA,SAAS,IAAgB;EACrB,IAAI,GAAQ;EAEZ,AADI,KAAQ,EAAO,MAAM,GACzB,EAAU,YAAY;EAEtB,IAAM,IAAK,IAAI,YAAY,GAAK,EAAE,mBAAgB,CAAC;EASnD,AARA,IAAS,GAET,EAAG,eAAe;GAGd,AAFA,IAAU,GACV,EAAU,MAAM,GAChB,IAAS;EACb,GAEA,EAAG,aAAa,MAAU,EAAK,WAAW,CAAK;EAC/C,KAAK,IAAM,KAAQ,GACf,EAAG,iBAAiB,IAAO,MAAU,EAAK,GAAM,CAAqB,CAAC;EAE1E,KAAK,IAAM,KAAQ,GACf,EAAG,iBAAiB,SAAY,CAEhC,CAAC;EAGL,EAAG,WAAW,MAAU;GAKpB,AAJA,IAAU,CAAK,GACf,EAAG,MAAM,GACT,IAAS,MACT,EAAU,QAAQ,GAClB,EAAkB;EACtB;CACJ;CAEA,SAAS,IAAc;EAWnB,AAVA,IAAS,IACT,AAEI,OADA,aAAa,CAAU,GACV,OAEjB,IAAU,GACV,AAEI,OADA,EAAO,MAAM,GACJ,OAEb,EAAU,QAAQ;CACtB;CAEA,SAAS,IAAkB;EAOvB,AANA,AAEI,OADA,aAAa,CAAU,GACV,OAEjB,IAAU,GACV,IAAS,IACT,EAAQ;CACZ;CAIA,OAFA,EAAQ,GAED;EACH;EACA;EACA,IAAI,SAAS;GACT,OAAO;EACX;CACJ;AACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-event-stream.cjs","names":[],"sources":["../../src/sse/use-event-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createEventStream,\n type CreateEventStreamOptions,\n type EventStreamMessage,\n type EventStreamStatus,\n} from \"./create-event-stream\";\n\nexport interface UseEventStreamOptions<T> extends Omit<\n CreateEventStreamOptions<T>,\n \"onStatusChange\"\n> {\n /** When false, the stream is not opened. Useful for \"wait for auth\". Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseEventStreamResult<T> {\n status: EventStreamStatus;\n /** Last message received (excluding heartbeats). */\n lastMessage: EventStreamMessage<T> | null;\n /** Force a reconnect. */\n reconnect: () => void;\n}\n\n/**\n * React hook wrapper around {@link createEventStream}. Connection lifecycle is\n * tied to the component (and the `url`/`enabled` dependencies); the stream\n * closes on unmount.\n *\n * @example\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * enabled: !!user,\n * withCredentials: true,\n * onMessage: ({ data }) => addNotification(data),\n * });\n */\nexport function useEventStream<T = unknown>(\n url: string,\n options: UseEventStreamOptions<T> = {},\n): UseEventStreamResult<T> {\n const { enabled = true, onMessage, ...rest } = options;\n const [status, setStatus] = useState<EventStreamStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<EventStreamMessage<T> | null>(null);\n const reconnectRef = useRef<(() => void) | null>(null);\n\n const onMessageRef = useLatestRef(onMessage);\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n const controller = createEventStream<T>(url, {\n ...rest,\n onStatusChange: setStatus,\n onMessage: (message) => {\n setLastMessage(message);\n onMessageRef.current?.(message);\n },\n });\n reconnectRef.current = controller.reconnect;\n\n return () => {\n controller.close();\n reconnectRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, enabled]);\n\n return {\n status,\n lastMessage,\n reconnect: () => reconnectRef.current?.(),\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-event-stream.cjs","names":[],"sources":["../../src/sse/use-event-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createEventStream,\n type CreateEventStreamOptions,\n type EventStreamMessage,\n type EventStreamStatus,\n} from \"./create-event-stream\";\n\nexport interface UseEventStreamOptions<T> extends Omit<\n CreateEventStreamOptions<T>,\n \"onStatusChange\"\n> {\n /** When false, the stream is not opened. Useful for \"wait for auth\". Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseEventStreamResult<T> {\n status: EventStreamStatus;\n /** Last message received (excluding heartbeats). */\n lastMessage: EventStreamMessage<T> | null;\n /** Force a reconnect. */\n reconnect: () => void;\n}\n\n/**\n * React hook wrapper around {@link createEventStream}. Connection lifecycle is\n * tied to the component (and the `url`/`enabled` dependencies); the stream\n * closes on unmount.\n *\n * Pass `schema` to have every frame validated before it reaches `onMessage`,\n * instead of trusting the type argument: without it `data` is announced as `T`\n * on the strength of the generic alone, which is a promise about the server\n * that TypeScript cannot keep. It is read when the stream opens, so declare it\n * outside the component rather than building one inline per render.\n *\n * @example\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * enabled: !!user,\n * withCredentials: true,\n * onMessage: ({ data }) => addNotification(data),\n * });\n *\n * @example\n * // Validated: a frame that does not match never reaches `onMessage`\n * const notificationSchema = z.object({ id: z.string(), message: z.string() });\n *\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * schema: notificationSchema,\n * onValidationError: (issues) => logger.warn(\"stream drift\", { issues }),\n * onMessage: ({ data }) => addNotification(data),\n * });\n */\nexport function useEventStream<T = unknown>(\n url: string,\n options: UseEventStreamOptions<T> = {},\n): UseEventStreamResult<T> {\n const { enabled = true, onMessage, ...rest } = options;\n const [status, setStatus] = useState<EventStreamStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<EventStreamMessage<T> | null>(null);\n const reconnectRef = useRef<(() => void) | null>(null);\n\n const onMessageRef = useLatestRef(onMessage);\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n const controller = createEventStream<T>(url, {\n ...rest,\n onStatusChange: setStatus,\n onMessage: (message) => {\n setLastMessage(message);\n onMessageRef.current?.(message);\n },\n });\n reconnectRef.current = controller.reconnect;\n\n return () => {\n controller.close();\n reconnectRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, enabled]);\n\n return {\n status,\n lastMessage,\n reconnect: () => reconnectRef.current?.(),\n };\n}\n"],"mappings":"6GAqDA,SAAgB,EACZ,EACA,EAAoC,CAAC,EACd,CACvB,GAAM,CAAE,UAAU,GAAM,YAAW,GAAG,GAAS,EACzC,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA4B,MAAM,EACxD,CAAC,EAAa,IAAA,EAAkB,EAAA,SAAA,CAAuC,IAAI,EAC3E,GAAA,EAAe,EAAA,OAAA,CAA4B,IAAI,EAE/C,EAAe,EAAA,aAAa,CAAS,EAyB3C,OAvBA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,GAAW,CAAC,EAAK,CAClB,EAAU,MAAM,EAChB,MACJ,CAEA,IAAM,EAAa,EAAA,kBAAqB,EAAK,CACzC,GAAG,EACH,eAAgB,EAChB,UAAY,GAAY,CACpB,EAAe,CAAO,EACtB,EAAa,UAAU,CAAO,CAClC,CACJ,CAAC,EAGD,MAFA,GAAa,QAAU,EAAW,cAErB,CACT,EAAW,MAAM,EACjB,EAAa,QAAU,IAC3B,CAEJ,EAAG,CAAC,EAAK,CAAO,CAAC,EAEV,CACH,SACA,cACA,cAAiB,EAAa,UAAU,CAC5C,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-event-stream.js","names":[],"sources":["../../src/sse/use-event-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createEventStream,\n type CreateEventStreamOptions,\n type EventStreamMessage,\n type EventStreamStatus,\n} from \"./create-event-stream\";\n\nexport interface UseEventStreamOptions<T> extends Omit<\n CreateEventStreamOptions<T>,\n \"onStatusChange\"\n> {\n /** When false, the stream is not opened. Useful for \"wait for auth\". Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseEventStreamResult<T> {\n status: EventStreamStatus;\n /** Last message received (excluding heartbeats). */\n lastMessage: EventStreamMessage<T> | null;\n /** Force a reconnect. */\n reconnect: () => void;\n}\n\n/**\n * React hook wrapper around {@link createEventStream}. Connection lifecycle is\n * tied to the component (and the `url`/`enabled` dependencies); the stream\n * closes on unmount.\n *\n * @example\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * enabled: !!user,\n * withCredentials: true,\n * onMessage: ({ data }) => addNotification(data),\n * });\n */\nexport function useEventStream<T = unknown>(\n url: string,\n options: UseEventStreamOptions<T> = {},\n): UseEventStreamResult<T> {\n const { enabled = true, onMessage, ...rest } = options;\n const [status, setStatus] = useState<EventStreamStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<EventStreamMessage<T> | null>(null);\n const reconnectRef = useRef<(() => void) | null>(null);\n\n const onMessageRef = useLatestRef(onMessage);\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n const controller = createEventStream<T>(url, {\n ...rest,\n onStatusChange: setStatus,\n onMessage: (message) => {\n setLastMessage(message);\n onMessageRef.current?.(message);\n },\n });\n reconnectRef.current = controller.reconnect;\n\n return () => {\n controller.close();\n reconnectRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, enabled]);\n\n return {\n status,\n lastMessage,\n reconnect: () => reconnectRef.current?.(),\n };\n}\n"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"use-event-stream.js","names":[],"sources":["../../src/sse/use-event-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createEventStream,\n type CreateEventStreamOptions,\n type EventStreamMessage,\n type EventStreamStatus,\n} from \"./create-event-stream\";\n\nexport interface UseEventStreamOptions<T> extends Omit<\n CreateEventStreamOptions<T>,\n \"onStatusChange\"\n> {\n /** When false, the stream is not opened. Useful for \"wait for auth\". Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseEventStreamResult<T> {\n status: EventStreamStatus;\n /** Last message received (excluding heartbeats). */\n lastMessage: EventStreamMessage<T> | null;\n /** Force a reconnect. */\n reconnect: () => void;\n}\n\n/**\n * React hook wrapper around {@link createEventStream}. Connection lifecycle is\n * tied to the component (and the `url`/`enabled` dependencies); the stream\n * closes on unmount.\n *\n * Pass `schema` to have every frame validated before it reaches `onMessage`,\n * instead of trusting the type argument: without it `data` is announced as `T`\n * on the strength of the generic alone, which is a promise about the server\n * that TypeScript cannot keep. It is read when the stream opens, so declare it\n * outside the component rather than building one inline per render.\n *\n * @example\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * enabled: !!user,\n * withCredentials: true,\n * onMessage: ({ data }) => addNotification(data),\n * });\n *\n * @example\n * // Validated: a frame that does not match never reaches `onMessage`\n * const notificationSchema = z.object({ id: z.string(), message: z.string() });\n *\n * useEventStream<Notification>(`${API}/notifications/stream`, {\n * schema: notificationSchema,\n * onValidationError: (issues) => logger.warn(\"stream drift\", { issues }),\n * onMessage: ({ data }) => addNotification(data),\n * });\n */\nexport function useEventStream<T = unknown>(\n url: string,\n options: UseEventStreamOptions<T> = {},\n): UseEventStreamResult<T> {\n const { enabled = true, onMessage, ...rest } = options;\n const [status, setStatus] = useState<EventStreamStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<EventStreamMessage<T> | null>(null);\n const reconnectRef = useRef<(() => void) | null>(null);\n\n const onMessageRef = useLatestRef(onMessage);\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n const controller = createEventStream<T>(url, {\n ...rest,\n onStatusChange: setStatus,\n onMessage: (message) => {\n setLastMessage(message);\n onMessageRef.current?.(message);\n },\n });\n reconnectRef.current = controller.reconnect;\n\n return () => {\n controller.close();\n reconnectRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, enabled]);\n\n return {\n status,\n lastMessage,\n reconnect: () => reconnectRef.current?.(),\n };\n}\n"],"mappings":";;;;AAqDA,SAAgB,EACZ,GACA,IAAoC,CAAC,GACd;CACvB,IAAM,EAAE,aAAU,IAAM,cAAW,GAAG,MAAS,GACzC,CAAC,GAAQ,KAAa,EAA4B,MAAM,GACxD,CAAC,GAAa,KAAkB,EAAuC,IAAI,GAC3E,IAAe,EAA4B,IAAI,GAE/C,IAAe,EAAa,CAAS;CAyB3C,OAvBA,QAAgB;EACZ,IAAI,CAAC,KAAW,CAAC,GAAK;GAClB,EAAU,MAAM;GAChB;EACJ;EAEA,IAAM,IAAa,EAAqB,GAAK;GACzC,GAAG;GACH,gBAAgB;GAChB,YAAY,MAAY;IAEpB,AADA,EAAe,CAAO,GACtB,EAAa,UAAU,CAAO;GAClC;EACJ,CAAC;EAGD,OAFA,EAAa,UAAU,EAAW,iBAErB;GAET,AADA,EAAW,MAAM,GACjB,EAAa,UAAU;EAC3B;CAEJ,GAAG,CAAC,GAAK,CAAO,CAAC,GAEV;EACH;EACA;EACA,iBAAiB,EAAa,UAAU;CAC5C;AACJ"}
|
|
@@ -3236,6 +3236,34 @@ export declare interface CreateEventStreamOptions<T> {
|
|
|
3236
3236
|
* kept, with a one-time warning in development builds.
|
|
3237
3237
|
*/
|
|
3238
3238
|
onParseError?: (error: unknown, raw: string) => void;
|
|
3239
|
+
/**
|
|
3240
|
+
* Schema every decoded frame must satisfy, from zod, valibot, arktype or
|
|
3241
|
+
* anything else exposing `~standard` or `.safeParse`.
|
|
3242
|
+
*
|
|
3243
|
+
* Without it nothing changes: the payload reaches `onMessage` announced as
|
|
3244
|
+
* `T` on the strength of the type argument alone, which is a promise about
|
|
3245
|
+
* the server that TypeScript cannot keep. With it, a frame that does not
|
|
3246
|
+
* match is **not** delivered — the same rule `onParseError` already follows —
|
|
3247
|
+
* and `onValidationError` hears why. The value delivered is the schema's
|
|
3248
|
+
* output, so coercions and defaults are honoured.
|
|
3249
|
+
*
|
|
3250
|
+
* When `parser` is also supplied, it decodes first and the schema validates
|
|
3251
|
+
* what it returned.
|
|
3252
|
+
*
|
|
3253
|
+
* The validation must be synchronous. A frame is decoded inside the
|
|
3254
|
+
* `message` handler and delivered from it, so an async schema would deliver
|
|
3255
|
+
* frames in whatever order their validations settled; that case is reported
|
|
3256
|
+
* through `onValidationError` instead of awaited.
|
|
3257
|
+
*/
|
|
3258
|
+
schema?: SchemaLike<T>;
|
|
3259
|
+
/**
|
|
3260
|
+
* A frame was decoded but the `schema` refused it, so it was dropped.
|
|
3261
|
+
*
|
|
3262
|
+
* The one signal that does not depend on how the app's bundler resolves
|
|
3263
|
+
* `process`: the one-time development warning behind `onParseError` needs
|
|
3264
|
+
* `isDevBuild()` to be able to answer, and this callback is the app's own.
|
|
3265
|
+
*/
|
|
3266
|
+
onValidationError?: (issues: SchemaIssue[], raw: string) => void;
|
|
3239
3267
|
onOpen?: () => void;
|
|
3240
3268
|
onMessage?: (message: EventStreamMessage<T>) => void;
|
|
3241
3269
|
onError?: (error: Event) => void;
|
|
@@ -4420,6 +4448,37 @@ export declare interface CreateWebSocketOptions<T> {
|
|
|
4420
4448
|
* kept, with a one-time warning in development builds.
|
|
4421
4449
|
*/
|
|
4422
4450
|
onParseError?: (error: unknown, raw: string) => void;
|
|
4451
|
+
/**
|
|
4452
|
+
* Schema every decoded frame must satisfy, from zod, valibot, arktype or
|
|
4453
|
+
* anything else exposing `~standard` or `.safeParse`.
|
|
4454
|
+
*
|
|
4455
|
+
* Without it nothing changes: the payload reaches `onMessage` announced as
|
|
4456
|
+
* `T` on the strength of the type argument alone, which is a promise about
|
|
4457
|
+
* the server that TypeScript cannot keep. With it, a frame that does not
|
|
4458
|
+
* match is **not** delivered — the same rule `onParseError` already follows —
|
|
4459
|
+
* and `onValidationError` hears why. The value delivered is the schema's
|
|
4460
|
+
* output, so coercions and defaults are honoured.
|
|
4461
|
+
*
|
|
4462
|
+
* When `parser` is also supplied, it decodes first and the schema validates
|
|
4463
|
+
* what it returned.
|
|
4464
|
+
*
|
|
4465
|
+
* A server ping is still answered when the schema drops it: the heartbeat is
|
|
4466
|
+
* the transport's contract with the server, not the app's with its payload,
|
|
4467
|
+
* and a socket that stops sending `pong` is closed with `4408` once per
|
|
4468
|
+
* timeout. The validation itself must be synchronous — a frame is decoded
|
|
4469
|
+
* inside the `message` handler and delivered from it, so an async schema
|
|
4470
|
+
* would deliver frames in whatever order their validations settled; that
|
|
4471
|
+
* case is reported through `onValidationError` instead of awaited.
|
|
4472
|
+
*/
|
|
4473
|
+
schema?: SchemaLike<T>;
|
|
4474
|
+
/**
|
|
4475
|
+
* A frame was decoded but the `schema` refused it, so it was dropped.
|
|
4476
|
+
*
|
|
4477
|
+
* The one signal that does not depend on how the app's bundler resolves
|
|
4478
|
+
* `process`: the one-time development warning behind `onParseError` needs
|
|
4479
|
+
* `isDevBuild()` to be able to answer, and this callback is the app's own.
|
|
4480
|
+
*/
|
|
4481
|
+
onValidationError?: (issues: SchemaIssue[], raw: string) => void;
|
|
4423
4482
|
onOpen?: (event: Event) => void;
|
|
4424
4483
|
onMessage?: (message: WebSocketMessage<T>) => void;
|
|
4425
4484
|
onClose?: (event: CloseEvent) => void;
|
|
@@ -5732,7 +5791,7 @@ export declare interface EventStreamController {
|
|
|
5732
5791
|
export declare interface EventStreamMessage<T> {
|
|
5733
5792
|
/** Server-named event (default `"message"`). */
|
|
5734
5793
|
event: string;
|
|
5735
|
-
/** Parsed payload — JSON-decoded when possible, raw string otherwise. */
|
|
5794
|
+
/** Parsed payload — validated when `schema` is set, JSON-decoded when possible, raw string otherwise. */
|
|
5736
5795
|
data: T;
|
|
5737
5796
|
/** Server-supplied id, if any. */
|
|
5738
5797
|
id?: string;
|
|
@@ -7454,6 +7513,11 @@ export declare function isStandalone(): boolean;
|
|
|
7454
7513
|
*/
|
|
7455
7514
|
export declare function isString(value: unknown): value is string;
|
|
7456
7515
|
|
|
7516
|
+
/** A path segment as Standard Schema reports it: a key, or an object holding one. */
|
|
7517
|
+
declare type IssuePathSegment = PropertyKey | {
|
|
7518
|
+
readonly key: PropertyKey;
|
|
7519
|
+
};
|
|
7520
|
+
|
|
7457
7521
|
/** True when `value` is a finite latitude in `[-90, 90]`. */
|
|
7458
7522
|
export declare function isValidLatitude(value: number): boolean;
|
|
7459
7523
|
|
|
@@ -10882,6 +10946,12 @@ export declare interface RatingStarsProps {
|
|
|
10882
10946
|
className?: string;
|
|
10883
10947
|
}
|
|
10884
10948
|
|
|
10949
|
+
/** One issue as either supported shape reports it. */
|
|
10950
|
+
declare interface RawIssue {
|
|
10951
|
+
readonly message: string;
|
|
10952
|
+
readonly path?: readonly IssuePathSegment[] | undefined;
|
|
10953
|
+
}
|
|
10954
|
+
|
|
10885
10955
|
/**
|
|
10886
10956
|
* Pick the readable foreground for a background, by contrast ratio.
|
|
10887
10957
|
*
|
|
@@ -11617,6 +11687,19 @@ export declare interface SafeAreaProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
11617
11687
|
children?: ReactNode;
|
|
11618
11688
|
}
|
|
11619
11689
|
|
|
11690
|
+
/** A schema exposing zod's `.safeParse`, including versions older than `~standard`. */
|
|
11691
|
+
export declare interface SafeParseSchemaLike<T> {
|
|
11692
|
+
readonly safeParse: (value: unknown) => {
|
|
11693
|
+
readonly success: true;
|
|
11694
|
+
readonly data: T;
|
|
11695
|
+
} | {
|
|
11696
|
+
readonly success: false;
|
|
11697
|
+
readonly error: {
|
|
11698
|
+
readonly issues: readonly RawIssue[];
|
|
11699
|
+
};
|
|
11700
|
+
};
|
|
11701
|
+
}
|
|
11702
|
+
|
|
11620
11703
|
/**
|
|
11621
11704
|
* Divide the video caps by the size of the room.
|
|
11622
11705
|
*
|
|
@@ -11720,6 +11803,36 @@ export declare interface SchedulerProps extends Omit<HTMLAttributes<HTMLDivEleme
|
|
|
11720
11803
|
now?: Date;
|
|
11721
11804
|
}
|
|
11722
11805
|
|
|
11806
|
+
/**
|
|
11807
|
+
* The SDK's answer to "the caller handed me a schema" — one normalizer behind
|
|
11808
|
+
* every option that takes one.
|
|
11809
|
+
*
|
|
11810
|
+
* Internal, and imported by path rather than through the `utils` barrel: the
|
|
11811
|
+
* function exists so `decodeFrame` and anything else that validates a payload
|
|
11812
|
+
* share one reading of the two shapes below, not so consumers can call it. The
|
|
11813
|
+
* types are public, because an option typed `SchemaLike<T>` is a name the
|
|
11814
|
+
* consumer has to be able to write down.
|
|
11815
|
+
*
|
|
11816
|
+
* Two shapes are accepted on purpose:
|
|
11817
|
+
*
|
|
11818
|
+
* - [Standard Schema](https://standardschema.dev) (`~standard`), which zod
|
|
11819
|
+
* (>=3.24), valibot and arktype all implement, so the SDK validates against
|
|
11820
|
+
* any of them without depending on one;
|
|
11821
|
+
* - `.safeParse`, because the SDK's own zod range starts at `^3.23.0`, which
|
|
11822
|
+
* predates `~standard`, and because it is the method every zod user already
|
|
11823
|
+
* knows.
|
|
11824
|
+
*/
|
|
11825
|
+
/** One field-level complaint from a schema validation. */
|
|
11826
|
+
export declare interface SchemaIssue {
|
|
11827
|
+
/** Dotted path to the offending field, or `"<root>"` for the value itself. */
|
|
11828
|
+
path: string;
|
|
11829
|
+
/** What the validator said was wrong. */
|
|
11830
|
+
message: string;
|
|
11831
|
+
}
|
|
11832
|
+
|
|
11833
|
+
/** Anything the SDK can validate a payload against. */
|
|
11834
|
+
export declare type SchemaLike<T> = StandardSchemaLike<T> | SafeParseSchemaLike<T>;
|
|
11835
|
+
|
|
11723
11836
|
/** Lifecycle of a screen share. */
|
|
11724
11837
|
export declare type ScreenCaptureStatus = "idle" | "requesting" | "sharing" | "error";
|
|
11725
11838
|
|
|
@@ -11912,13 +12025,13 @@ export declare function setAudioOutput(element: HTMLMediaElement | null, sinkId:
|
|
|
11912
12025
|
* the SDK routes through it — `grep -rn "dev-mode" src/` for the current list,
|
|
11913
12026
|
* which an enumeration written here would only drift away from.
|
|
11914
12027
|
*
|
|
11915
|
-
* {@link setDevBuild} is public, because
|
|
11916
|
-
*
|
|
12028
|
+
* {@link setDevBuild} is public, because a context nothing compiles cannot be
|
|
12029
|
+
* detected from the inside. See its doc for when that is.
|
|
11917
12030
|
*/
|
|
11918
12031
|
/**
|
|
11919
12032
|
* Tell the SDK whether the app around it was built for development.
|
|
11920
12033
|
*
|
|
11921
|
-
* Call it once, at bootstrap, from a
|
|
12034
|
+
* Call it once, at bootstrap, from a context {@link isDevBuild} cannot read:
|
|
11922
12035
|
*
|
|
11923
12036
|
* ```ts
|
|
11924
12037
|
* import { setDevBuild } from "tempest-react-sdk";
|
|
@@ -11926,20 +12039,29 @@ export declare function setAudioOutput(element: HTMLMediaElement | null, sinkId:
|
|
|
11926
12039
|
* setDevBuild(import.meta.env.DEV);
|
|
11927
12040
|
* ```
|
|
11928
12041
|
*
|
|
11929
|
-
* **
|
|
11930
|
-
*
|
|
11931
|
-
*
|
|
11932
|
-
*
|
|
11933
|
-
*
|
|
11934
|
-
*
|
|
11935
|
-
*
|
|
11936
|
-
*
|
|
11937
|
-
*
|
|
11938
|
-
*
|
|
11939
|
-
*
|
|
11940
|
-
*
|
|
11941
|
-
*
|
|
11942
|
-
*
|
|
12042
|
+
* **When you need it.** Not for an ordinary Vite, webpack, Rspack or Parcel
|
|
12043
|
+
* app: all of them substitute `process.env.NODE_ENV` while building the app, so
|
|
12044
|
+
* the automatic read already answers correctly there — measured for Vite 5
|
|
12045
|
+
* through 8 in {@link isDevBuild}, whose doc carries the table. What is left is
|
|
12046
|
+
* the context nothing compiles or nothing configures:
|
|
12047
|
+
*
|
|
12048
|
+
* - code no bundler transformed — a raw service-worker script registered as a
|
|
12049
|
+
* file of its own, a plain `<script type="module">`;
|
|
12050
|
+
* - a staging or QA build that never sets `NODE_ENV=production`, where the
|
|
12051
|
+
* automatic answer is `true` and `parseResponse` would put the raw response
|
|
12052
|
+
* payload in an error string seen by real users. `setDevBuild(false)` closes
|
|
12053
|
+
* that;
|
|
12054
|
+
* - a test that wants the other branch, and puts it back on the way out.
|
|
12055
|
+
*
|
|
12056
|
+
* `import.meta.env.DEV` cannot be read by the SDK on your behalf, which is why
|
|
12057
|
+
* the signal is a parameter: Vite would replace it while building *this
|
|
12058
|
+
* package*, and the published artifact would ship the constant.
|
|
12059
|
+
*
|
|
12060
|
+
* The default stays `false` when the read throws. `parseResponse` puts the raw
|
|
12061
|
+
* response payload in its message when this is on, so guessing `true` in a
|
|
12062
|
+
* context that cannot prove it leaks a payload into a production error string.
|
|
12063
|
+
* Silence is the safe default; the report is one line away for anyone who wants
|
|
12064
|
+
* it.
|
|
11943
12065
|
*
|
|
11944
12066
|
* Passing `undefined` clears the override and returns to automatic detection,
|
|
11945
12067
|
* which is what a test that set it should do on the way out.
|
|
@@ -11948,8 +12070,8 @@ export declare function setAudioOutput(element: HTMLMediaElement | null, sinkId:
|
|
|
11948
12070
|
* `undefined` to go back to detecting it.
|
|
11949
12071
|
*
|
|
11950
12072
|
* @example
|
|
11951
|
-
* //
|
|
11952
|
-
* setDevBuild(
|
|
12073
|
+
* // A service worker, or any context no bundler transformed
|
|
12074
|
+
* setDevBuild(false);
|
|
11953
12075
|
*
|
|
11954
12076
|
* @example
|
|
11955
12077
|
* // A test that flips it, and puts it back
|
|
@@ -12621,6 +12743,23 @@ export declare const STALE_TIME: {
|
|
|
12621
12743
|
readonly INFINITE: number;
|
|
12622
12744
|
};
|
|
12623
12745
|
|
|
12746
|
+
/** A schema exposing the [Standard Schema](https://standardschema.dev) interface. */
|
|
12747
|
+
export declare interface StandardSchemaLike<T> {
|
|
12748
|
+
readonly "~standard": {
|
|
12749
|
+
readonly validate: (value: unknown) => {
|
|
12750
|
+
readonly value: T;
|
|
12751
|
+
readonly issues?: undefined;
|
|
12752
|
+
} | {
|
|
12753
|
+
readonly issues: readonly RawIssue[];
|
|
12754
|
+
} | Promise<{
|
|
12755
|
+
readonly value: T;
|
|
12756
|
+
readonly issues?: undefined;
|
|
12757
|
+
} | {
|
|
12758
|
+
readonly issues: readonly RawIssue[];
|
|
12759
|
+
}>;
|
|
12760
|
+
};
|
|
12761
|
+
}
|
|
12762
|
+
|
|
12624
12763
|
/**
|
|
12625
12764
|
* KPI card. Dashboard widget showing a label + big value + optional
|
|
12626
12765
|
* delta/trend and hint.
|
|
@@ -14648,12 +14787,28 @@ export declare function useEventListener<K extends keyof HTMLElementEventMap>(ev
|
|
|
14648
14787
|
* tied to the component (and the `url`/`enabled` dependencies); the stream
|
|
14649
14788
|
* closes on unmount.
|
|
14650
14789
|
*
|
|
14790
|
+
* Pass `schema` to have every frame validated before it reaches `onMessage`,
|
|
14791
|
+
* instead of trusting the type argument: without it `data` is announced as `T`
|
|
14792
|
+
* on the strength of the generic alone, which is a promise about the server
|
|
14793
|
+
* that TypeScript cannot keep. It is read when the stream opens, so declare it
|
|
14794
|
+
* outside the component rather than building one inline per render.
|
|
14795
|
+
*
|
|
14651
14796
|
* @example
|
|
14652
14797
|
* useEventStream<Notification>(`${API}/notifications/stream`, {
|
|
14653
14798
|
* enabled: !!user,
|
|
14654
14799
|
* withCredentials: true,
|
|
14655
14800
|
* onMessage: ({ data }) => addNotification(data),
|
|
14656
14801
|
* });
|
|
14802
|
+
*
|
|
14803
|
+
* @example
|
|
14804
|
+
* // Validated: a frame that does not match never reaches `onMessage`
|
|
14805
|
+
* const notificationSchema = z.object({ id: z.string(), message: z.string() });
|
|
14806
|
+
*
|
|
14807
|
+
* useEventStream<Notification>(`${API}/notifications/stream`, {
|
|
14808
|
+
* schema: notificationSchema,
|
|
14809
|
+
* onValidationError: (issues) => logger.warn("stream drift", { issues }),
|
|
14810
|
+
* onMessage: ({ data }) => addNotification(data),
|
|
14811
|
+
* });
|
|
14657
14812
|
*/
|
|
14658
14813
|
export declare function useEventStream<T = unknown>(url: string, options?: UseEventStreamOptions<T>): UseEventStreamResult<T>;
|
|
14659
14814
|
|
|
@@ -16803,6 +16958,10 @@ export { useWatch }
|
|
|
16803
16958
|
* baked into the connection, so changing one reopens it with the new value
|
|
16804
16959
|
* rather than being silently ignored.
|
|
16805
16960
|
*
|
|
16961
|
+
* `schema` is read when the socket opens, so declare it outside the component
|
|
16962
|
+
* (or memoize it): a schema built inline is a new object on every render, and
|
|
16963
|
+
* the one in force is whichever existed at the last open.
|
|
16964
|
+
*
|
|
16806
16965
|
* @param url - Full ws:// or wss:// URL.
|
|
16807
16966
|
* @param options - Connection configuration and callbacks.
|
|
16808
16967
|
* @returns Status, last frame, and the `send` / `reconnect` controls.
|
|
@@ -17717,7 +17876,7 @@ export declare interface WebSocketController {
|
|
|
17717
17876
|
export declare type WebSocketLostReason = "rejected" | "exhausted";
|
|
17718
17877
|
|
|
17719
17878
|
export declare interface WebSocketMessage<T> {
|
|
17720
|
-
/** Parsed payload — JSON-decoded when possible, raw string otherwise. */
|
|
17879
|
+
/** Parsed payload — validated when `schema` is set, JSON-decoded when possible, raw string otherwise. */
|
|
17721
17880
|
data: T;
|
|
17722
17881
|
/** The original `MessageEvent`. */
|
|
17723
17882
|
raw: MessageEvent;
|