Agent skill

River TS Streaming

by Bewinxed in Bewinxed/river.ts

Type-safe Server-Sent Events (SSE) and WebSocket communication using river.ts library.

MITAuto-check passedBackend & APIs

Install River TS Streaming

skills CLI
$ npx skills add Bewinxed/river.ts --skill river-ts-streaming -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install Bewinxed/river.ts river-ts-streaming --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
river-ts-streaming
GitHub stars
109
Token cost
~2.5k tokens
SKILL.md length
435 words
Files
53
Skills in repo
1
Repo updated
First seen
Licence
MIT

At a glance

Type-safe Server-Sent Events (SSE) and WebSocket communication using river.ts library.

  • Working with this codebase to:
  • SKILL.md covers Quick Reference, Event Definition, Server-Side SSE (RiverEmitter) and Client-Side SSE (RiverClient), plus 6 more sections
  • Runs TypeScript and JavaScript scripts from its folder; calls npm, bunx and bun
  • Define typed event schemas with RiverEvents builder

What it does

River TS Streaming is an agent skill from Bewinxed/river.ts. Type-safe Server-Sent Events (SSE) and WebSocket communication using river.ts library. Use when working with this codebase to: (1) Define typed event schemas with RiverEvents builder, (2) Implement SSE streaming on server with RiverEmitter, (3) Consume SSE streams on client with RiverClient, (4) Handle WebSocket communication with RiverSocketAdapter, (5) Implement request/response RPC patterns over WebSocket, (6) Work with chunked/streamed data events.

Its SKILL.md is about 2.5k tokens, which your agent loads only when the skill is triggered. The skill folder holds 56 other files (for example `.github/workflows/publish.yml`, `README.md` and `build.config.ts`).

It sits in Backend & APIs, covering Realtime and WebSockets. It works with TypeScript. The repository describes itself as: Easy, Composable, and type-safe Server-Sent Events (SSE) & Websocket Messages. The licence is MIT.

When your agent uses it

  • Working with this codebase to:
  • Define typed event schemas with RiverEvents builder
  • Implement SSE streaming on server with RiverEmitter
  • Consume SSE streams on client with RiverClient

Example prompts

  • “/river-ts-streaming”

Requirements

  • Node.js

What it can do on your machine

Read from SKILL.md and the folder at commit 845ed95. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships script files (TypeScript and JavaScript, from the files we listed), which the agent can run.

    Shell commands in SKILL.md call:

    • npm
    • bunx
    • bun

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • standardschema.dev

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

River TS Streaming loads about 2.5k tokens when it runs. Until then it costs about 119 tokens; SKILL.md has 435 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~119
When it runs · the whole SKILL.md, loaded when a task matches
~2.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from Bewinxed/river.ts at commit 845ed95, republished under its MIT licence (© Bewinxed). 435 words, ~2,487 tokens.

Download SKILL.mdSave it as .claude/skills/river-ts-streaming/SKILL.md (or your agent's skills folder). This skill also uses 52 other files; get the full folder from GitHub.
name
river-ts-streaming
description
Type-safe Server-Sent Events (SSE) and WebSocket communication using river.ts library. Use when working with this codebase to: (1) Define typed event schemas with RiverEvents builder, (2) Implement SSE streaming on server with RiverEmitter, (3) Consume SSE streams on client with RiverClient, (4) Handle WebSocket communication with RiverSocketAdapter, (5) Implement request/response RPC patterns over WebSocket, (6) Work with chunked/streamed data events.

Quick Reference

river.ts provides three main components:

  • RiverEvents - Type-safe event schema builder
  • RiverEmitter - Server-side SSE streaming
  • RiverClient - Client-side SSE consumption
  • RiverSocketAdapter - WebSocket message handling with request/response support

Event Definition

Define events using the builder pattern:

typescript
import { RiverEvents } from 'river.ts';

const events = new RiverEvents()
  .defineEvent('message', { message: 'Hello' })
  .defineEvent('data', { data: {} as { id: number; name: string } })
  .defineEvent('stream', { data: [] as string[], stream: true, chunkSize: 100 })
  // Request/response pattern with explicit response type
  .defineEvent('rpc.call', {
    data: {} as { method: string; params: unknown },
    response: {} as { result: unknown; error?: string }
  })
  // Runtime validation: `data` is inferred from the schema's output
  .defineEvent('job.run', {
    schema: z.object({ id: z.string(), priority: z.number() }),
    responseSchema: z.object({ ok: z.boolean() }) // optional, for request()
  })
  .build();

Reserved event types: close, error - do not define these.

schema and responseSchema accept any Standard Schema (zod, valibot, arktype; https://standardschema.dev). Events without one are type-checked only.

Server-Side SSE (RiverEmitter)

typescript
import { RiverEmitter } from 'river.ts/server';

const emitter = RiverEmitter.init(events);

// Create SSE stream for HTTP response
const stream = emitter.stream({
  callback: async (emit, clientId) => {
    await emit('message', { message: 'Connected' });
    await emit('data', { data: { id: 1, name: 'test' } });
  },
  clientId: 'optional-custom-id',
  ondisconnect: (clientId) => console.log(`${clientId} disconnected`)
});

return new Response(stream, { headers: emitter.headers() });

// Broadcast to all clients
await emitter.broadcast('message', { message: 'Update' });

// Send to specific client
await emitter.sendToClient('client-id', 'data', { data: { id: 2, name: 'specific' } });

Resumable streams. All options are optional:

typescript
const stream = emitter.stream({
  retry: 3000,            // ms; written once as `retry:` when the stream opens
  keepAlive: 15_000,      // ms; writes a `: keep-alive` comment so proxies keep the connection
  lastEventId: request.headers.get('Last-Event-ID'), // read it from the request yourself
  signal: request.signal,
  callback: async (emit, clientId, lastEventId) => {
    // lastEventId is undefined on a first connection; replay everything after it
    for (const entry of log.after(lastEventId)) {
      await emit('message', { message: entry.text }, entry.id); // 3rd arg = event id
    }
  }
});

await emitter.broadcast('message', { message: 'Update' }, 42);          // optional id
await emitter.sendToClient('client-id', 'message', { message: 'x' }, 43); // optional id

Event ids are string | number and may be non-ASCII: stream() decodes the Last-Event-ID header as UTF-8, so the callback gets the id as it was emitted. For stream: true events the id is written after the last chunk.

Client-Side SSE (RiverClient)

typescript
import { RiverClient } from 'river.ts/client';

const client = RiverClient.init(events, { reconnect: true });

client
  .prepare('http://localhost:3000/events', { method: 'GET' })
  .on('message', (data) => console.log(data.message))
  .on('data', (data) => console.log(data.id, data.name))
  .stream();

// Close connection
client.close();
// stream() can be called again after close()

Client options (all optional; reconnect is off by default):

typescript
const client = RiverClient.init(events, {
  reconnect: true,                       // or { initialDelay: 1000, maxDelay: 30_000 }
  lastEventId: savedId,                  // sent as Last-Event-ID on the first connection
  onInvalid: (type, issues, raw) => {},  // events that fail their schema; never dispatched
  fetchFn: fetch,
  headers: { Authorization: 'Bearer ...' }
});

client.lastEventId; // read-only: id of the last event received; persist it to resume later
client.addEventListener('open', () => {});       // each successful connection
client.addEventListener('reconnect', (e) => {}); // (e as CustomEvent).detail = { attempt, delay, error }
client.addEventListener('close', () => {});      // stopped for good

Reconnect rules:

  • Retries: network errors, 5xx, 429, and a stream that ends without a close event.
  • Stops for good: HTTP 204, other 4xx, client.close(), the server's close event.
  • Delay: Retry-After (429/503), else the server's retry: value, else exponential backoff with jitter, capped at maxDelay.
  • Every reconnect sends Last-Event-ID (fetch path). A GET with no headers at all (none in init(), none in prepare()) uses the browser EventSource, which reconnects and resumes by itself; any header, another method or an initial lastEventId selects fetch.

The parser follows the WHATWG event-stream rules: CRLF/LF/CR line endings, : comments, event/data/id/retry fields, multiple data: lines joined with \n, default type message. Event data must be JSON.

Show full SKILL.md (205 more words)Show less

WebSocket Adapter (RiverSocketAdapter)

typescript
import { RiverSocketAdapter } from 'river.ts/websocket';

const adapter = new RiverSocketAdapter(events, { debug: false });

// Register event handlers
adapter.on('message', (data) => console.log(data));
adapter.off('message', handler); // Unregister

// Handle incoming messages (call from ws.onmessage)
adapter.handleMessage(messageData);

// Send messages
adapter.send('data', { data: { id: 1, name: 'test' } }, (msg) => ws.send(msg));

Runtime validation (events with a schema):

typescript
import { RiverSocketAdapter, InvalidMessageError } from 'river.ts/websocket';

const adapter = new RiverSocketAdapter(events, {
  onInvalid: (type, issues, raw) => console.warn(type, issues, raw)
});
  • handleMessage() validates data against the event's schema; an invalid message goes to onInvalid and is not dispatched. Without onInvalid it is logged with console.warn.
  • request() validates the response against responseSchema (or schema when the event has neither responseSchema nor a response type) and rejects with InvalidMessageError (.type, .issues) when invalid.
  • Handlers receive the schema's output. Async schemas are supported and arrival order is kept.
  • RiverClient does the same for the data field of incoming SSE events.

WebSocket Request/Response Pattern

For RPC-style communication with automatic type inference:

typescript
import {
  RiverSocketAdapter,
  RequestTimeoutError,
  WebSocketClosedError
} from 'river.ts/websocket';

// Events with explicit response types
const events = new RiverEvents()
  .defineEvent('instance.spawn', {
    data: {} as { cwd: string },
    response: {} as { instanceId: string; status: 'created' | 'error' }
  })
  .build();

const adapter = new RiverSocketAdapter(events);

// Route messages through adapter
ws.onmessage = (e) => adapter.handleMessage(e.data);
ws.onclose = () => adapter.clearPendingRequests();

// Make request - response type is inferred from event definition
const response = await adapter.request(
  'instance.spawn',
  { cwd: '/app' },
  (msg) => ws.send(msg),
  10000 // timeout in ms (default: 30000)
);
// response is typed as { instanceId: string; status: 'created' | 'error' }

Wire format for request/response:

json
// Request (outgoing)
{ "type": "instance.spawn", "data": { "cwd": "/app" }, "id": "uuid" }

// Response (incoming) - server echoes back the id
{ "type": "instance.spawn", "data": { "instanceId": "123", "status": "created" }, "id": "uuid" }

Key Types

typescript
import { EventData, ResponseData, EmitPayload } from 'river.ts';

// EventData<T, K> - Extract data type for receiving/handling
// ResponseData<T, K> - Extract response type for request() return value
// EmitPayload<T, K> - Extract payload type for emitting (excludes type/stream/chunkSize/schema/responseSchema)
// InvalidHandler - (type, issues, raw) => void, the `onInvalid` signature
// InvalidMessageError - thrown by request() for a response that fails its schema

Project Structure

src/
├── index.ts          # Main exports (RiverEvents, types)
├── builder.ts        # RiverEvents builder class
├── validate.ts       # Standard Schema validation shared by client and websocket
├── client/           # RiverClient for SSE consumption
├── server/           # RiverEmitter for SSE streaming
├── websocket/        # RiverSocketAdapter for WebSocket
└── types/
    ├── core.ts       # BaseEvent, EventMap, EventData, ResponseData
    └── http.ts       # HTTPMethods type

Verification

There are no unit tests. Check a change with bunx tsc --noEmit, bun run build, and a live run against a real server.

Build and release

Build with: npm run build (uses unbuild)

Output goes to dist/ with separate entry points for /client, /server, /websocket.

Release by bumping the version in package.json and pushing to main: .github/workflows/publish.yml typechecks, builds and publishes to npm when the registry does not have that version yet. A prerelease version (one containing -, such as 1.3.0-test.1 from npm run bump:test) is published under the test dist-tag; any other version becomes latest. The workflow is the only publisher.

© Bewinxed, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 52 other files in the repository root of Bewinxed/river.ts.

  • SKILL.md
  • .github/workflows/publish.yml
  • .gitignore
  • .npmignore
  • LICENSE.txt
  • README.md
  • build.config.ts
  • bun.lockb
  • examples/example.ts
  • examples/svelte/.gitignore
  • examples/svelte/.npmrc
  • examples/svelte/.prettierignore
  • examples/svelte/.prettierrc
  • examples/svelte/README.md
  • examples/svelte/bun.lockb
  • examples/svelte/eslint.config.js
  • examples/svelte/package-lock.json
  • … and 36 more

Open the folder on GitHubat commit 845ed95

Compare with similar skills

River TS Streaming next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

River TS Streaming compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
River TS Streaming this skillBewinxed/river.ts109—~2.5kAutomated safety check: PassMIT
Gemini Live API Devgoogle-gemini/gemini-skills4.3k—~4.6kAutomated safety check: PassApache-2.0
Web3 PolymarketPolymarket/agent-skills1912 repos~2kAutomated safety check: PassNone
Trigger.dev Realtimepapermark/papermark9.2k—~1.7kAutomated safety check: PassCustom licence
Kkrpckunkunsh/kkrpc174—~3.1kAutomated safety check: PassMIT
Kkrpc Interopkunkunsh/kkrpc174—~1.8kAutomated safety check: PassMIT

Similar skills

  • Gemini Live API Dev

    google-gemini/gemini-skills

    Official

    A skill your agent uses when building real-time, bidirectional streaming applications with the Gemini Live API, or migrating legacy Live models (2.0/2.5/3.1) to Gemini 3.8 Live.

    4.3k GitHub stars~4.6k tokensUpdated yesterday
    Backend & APIsAuto-check passed
  • Web3 Polymarket

    Polymarket/agent-skills

    Polymarket integration for prediction market trading on Polygon.

    191 GitHub starsUsed in 2 repos~2k tokens
    Backend & APIsAuto-check passed
  • Trigger.dev Realtime

    papermark/papermark

    Shows how to subscribe to Trigger.dev task runs from the backend and from React for progress indicators, live dashboards, AI response streams and approval waits.

    9.2k GitHub stars~1.7k tokensUpdated 1 mo ago
    Backend & APIsAuto-check passed
  • Kkrpc

    kunkunsh/kkrpc

    A skill your agent uses when building TypeScript RPC with kkrpc stable APIs, choosing native Transport<RPCMessage adapters, or integrating validation, middleware, transferables, streaming, remote…

    174 GitHub stars~3.1k tokensUpdated 26 days ago
    Backend & APIsAuto-check passed
  • Kkrpc Interop

    kunkunsh/kkrpc

    A skill your agent uses when implementing kkrpc clients or servers in non-TypeScript languages, speaking the stable compact protocol, transports, and reference implementations in Go, Python, Rust…

    174 GitHub stars~1.8k tokensUpdated 26 days ago
    Backend & APIsAuto-check passed
  • Switchboard Oracle SDK

    internet-court/internet-court-skill

    Covers integrating Switchboard oracles on Solana: pull feeds, oracle quotes, VRF randomness and Surge WebSocket streaming, from both the TypeScript SDK and Rust.

    6.4k GitHub starsUsed in 3 repos~2.9k tokens
    Backend & APIsAuto-check passed

Works with

Categories

Questions about River TS Streaming

What does River TS Streaming do?

Type-safe Server-Sent Events (SSE) and WebSocket communication using river.ts library. ts.ts library.

When should I use River TS Streaming?

River TS Streaming fits situations like: working with this codebase to:; define typed event schemas with RiverEvents builder; implement SSE streaming on server with RiverEmitter; consume SSE streams on client with RiverClient.

How do I install River TS Streaming in Claude Code?

Run `npx skills add Bewinxed/river.ts --skill river-ts-streaming -a claude-code`. Or copy the skill folder (the Bewinxed/river.ts repository) into .claude/skills/river-ts-streaming in your project. Claude Code loads it when a task matches its description.

How do I install River TS Streaming in Codex?

Run `npx skills add Bewinxed/river.ts --skill river-ts-streaming -a codex`. Or copy the skill folder (the Bewinxed/river.ts repository) into .agents/skills/river-ts-streaming in your project. Codex loads it when a task matches its description.

Can I use River TS Streaming in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add Bewinxed/river.ts --skill river-ts-streaming -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/river-ts-streaming, .gemini/skills/river-ts-streaming, .github/skills/river-ts-streaming and .opencode/skills/river-ts-streaming in your project.

What does River TS Streaming need to run?

Going by SKILL.md and its folder, River TS Streaming needs TypeScript and JavaScript for the scripts in its folder and the command-line tools its instructions call (npm, bunx and bun). Our summary lists: Node.js.

Does River TS Streaming access the network?

SKILL.md names 1 domain. As links in the text: standardschema.dev. This is read from the text; nothing was executed.

Is River TS Streaming safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does River TS Streaming use?

River TS Streaming is published under the MIT licence (from the LICENSE file in the skill folder). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does River TS Streaming use?

About 2.5k tokens (SKILL.md is roughly 9.9k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to River TS Streaming?

Skills that share tags, products or a category with River TS Streaming: Gemini Live API Dev (google-gemini/gemini-skills, 4.3k stars), Web3 Polymarket (Polymarket/agent-skills, 191 stars), Trigger.dev Realtime (papermark/papermark, 9.2k stars) and Kkrpc (kunkunsh/kkrpc, 174 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains River TS Streaming?

Bewinxed (a GitHub user) maintains it in Bewinxed/river.ts, which has 109 GitHub stars. The repository was last updated on October 4, 2026.

Source: Bewinxed/river.ts on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.