HeliumTS is under active development. Expect bugs and breaking changes. If you find any issues, please report them in our GitHub
Version 0.10.2 uses HTTP RPC and adds typed streaming with useStreaming.
Typed Streaming
Use an async generator with the existing defineMethod API, then consume its typed chunks with useStreaming. Helium handles the HTTP request and incremental decoding; your component does not write fetch or reader code.
Open the runnable streaming demo. It needs no provider SDK, API key, or database, and includes a partial-error control.
Server method
1// src/server/streaming.ts2import { setTimeout as delay } from "node:timers/promises";3import { defineMethod } from "heliumts/server";45export const streamReply = defineMethod(async function* (6 { prompt }: { prompt: string }, ctx7) {8 for (const word of ["Hello ", prompt, "! ", "This reply arrives in chunks."]) {9 if (ctx.signal.aborted) return;10 await delay(60, undefined, { signal: ctx.signal });11 yield word;12 }13});
Keep implementations in src/server. Import generated method stubs from heliumts/server in components. Pass ctx.signal into upstream requests and cancellable work so disconnects and client cancellation stop the producer.
React component
1import { useStreaming } from "heliumts/client";2import { streamReply } from "heliumts/server";34export default function Reply() {5 const { chunks, text, isStreaming, error, stats, start, cancel, reset } =6 useStreaming(streamReply, { timeoutMs: 120_000 });78 return (9 <section>10 <button onClick={() => void start({ prompt: "Helium" })}>11 {isStreaming ? "Restart" : "Start"}12 </button>13 <button onClick={cancel} disabled={!isStreaming}>Cancel</button>14 <button onClick={reset}>Reset</button>15 <output>{text}</output>16 {error && <p role="alert">{error}</p>}17 <p>{chunks.length} typed chunks received</p>18 </section>19 );20}
Arguments and chunk types are inferred from the method. chunks contains every received value; text concatenates string chunks. For structured chunks, render chunks directly. stats contains the current request rate-limit information when available.
Lifecycle and errors
start(args)aborts the previous run, clears its output, and starts a fresh request.cancel()stops the active request and preserves received chunks without treating cancellation as an error.reset()cancels and clears the output and error state.- A failure after partial output leaves those chunks visible and sets
error. Streams are never replayed automatically. startresolves after errors are exposed in state andonError; a button handler does not need a rejection handler.- Unmounting cancels the hook's active request.
Options
1const stream = useStreaming(streamReply, {2 timeoutMs: 120_000,3 onChunk: (chunk) => console.log(chunk), // Inferred string here.4 onComplete: () => console.log("Reply complete"),5 onError: (message) => console.error(message),6});
timeoutMs is a positive finite idle timeout, defaulting to 120,000 ms. It limits idle time while waiting for the first response and between received data, rather than setting a total duration for a healthy stream.
SSR, authentication, and hosting
The hook can be imported and rendered during SSR; it starts no browser request until you call start. Start in an event handler or client effect, never while rendering. Streaming methods use the same request middleware and auth context as ordinary RPC methods.
Reverse proxies must flush incremental responses instead of buffering the whole reply. Configure their idle timeouts for the longest expected pause. A buffering proxy can make correct server yields appear all at once.
See the OpenAI useStreaming example for a complete server method and React component, including upstream cancellation and partial errors.