Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Contributing

Thanks for contributing!

## Prerequisites

- Node.js (this repo’s demo uses Vite; Node 20.19+ recommended)
- npm

## Install

From the repo root:

```bash
npm install
```

## Useful npm scripts (recommended)

Run these from the repo root (they’re defined in the root `package.json`):

```bash
# one command: install demo deps, build, then run server+client in parallel
npm run demo

# if you only need pieces
npm run demo:install
npm run demo:server
npm run demo:client

# library build + tests
npm run build
npm run test
npm run test:ci
npm run test:coverage
npm run test:ui
```

## Running the demo (manual verification)

The demo is split into two apps:

- `demo/server`: Express server that exposes streaming endpoints
- `demo/client`: Vite + React client that consumes the streams

### 1) Start the server

```bash
cd demo/server
npm install
npm run dev
```

By default it runs on `http://localhost:3001`.

Endpoints:

- `GET /sse`: Server-Sent Events stream
- `POST /http-stream`: HTTP POST that responds with `text/event-stream`
- `GET /health`: health check

### 2) Start the client

In a second terminal:

```bash
cd demo/client
npm install
npm run dev
```

The page will open automatically. It renders:

- `ReactTextStream` component using SSE
- `useTextStream(url, onEvent)` using SSE
- `useTextStream({ store: "http", data, ... })` using HTTP POST + event-stream

### 3) What to verify

- **SSE path**: streaming words appear and ends with a “completed” event
- **HTTP POST path**: streaming words appear; changing the “HTTP message” input changes the request body sent to the server
- **Reconnect button**: restarts the stream

## Local build

From the repo root:

```bash
npm run build
```

18 changes: 0 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,6 @@ A React library for streaming text using Server-Sent Events (SSE) with `text/eve

![Demo of rendering text event stream](./demo.gif)

### Run the demo locally

From the repository root:

```bash
npm install
npm run demo
```

This installs dependencies for [`demo/server`](./demo/server) and [`demo/client`](./demo/client), builds the library into `dist/`, then starts both in parallel: the SSE test server on [http://localhost:3001](http://localhost:3001) (endpoint `/sse`) and the Vite app (by default [http://localhost:5173](http://localhost:5173); Vite may open a browser automatically). The client loads `react-text-stream` via `file:../..`, matching how the published package resolves to `dist`.

Other scripts:

- **`npm run demo:server`** — SSE server only (`demo/server`, `npm run dev`).
- **`npm run demo:client`** — Vite dev server only (`demo/client`). Run **`npm run build`** at the repo root first so `dist/` exists for the linked package.
- **`npm run demo:install`** — install dependencies in both demo packages without starting servers.
- **`npm run demo:verify`** — build the library and run a production build of the demo client (useful to confirm the package layout before publish).

## Installation

```bash
Expand Down
51 changes: 46 additions & 5 deletions demo/client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import './index.css'

const config = {
url: 'http://localhost:3001/sse',
httpUrl: 'http://localhost:3001/http-stream',
onEvent: (event: { type: string; word: string }) =>
event.type === 'completed' ? undefined : `${event.word ?? ''} `,
}
Expand Down Expand Up @@ -34,11 +35,7 @@ function App() {
)
}

function Streams({
onEvent,
}: {
onEvent: (event: { type: string; word: string }) => string | undefined
}) {
function Streams({ onEvent }: { onEvent: (event: { type: string; word: string }) => string | undefined }) {
return (
<>
<section className="terminal-section">
Expand All @@ -53,6 +50,10 @@ function Streams({
<h1 className="terminal-header">useTextStream() Hook</h1>
<HookTextStream />
</section>
<section className="terminal-section">
<h1 className="terminal-header">useTextStream() Hook (HTTP POST + event-stream)</h1>
<HookHttpTextStream />
</section>
</>
)
}
Expand All @@ -67,3 +68,43 @@ function HookTextStream() {
</div>
)
}

function HookHttpTextStream() {
const [message, setMessage] = useState('hello from client')
const [submittedMessage, setSubmittedMessage] = useState<string | undefined>(undefined)
const stream = useTextStream({
url: config.httpUrl,
store: 'http',
data: submittedMessage === undefined ? undefined : { message: submittedMessage },
onEvent: config.onEvent,
})
return (
<>
<div style={{ marginBottom: 12 }}>
<label>
HTTP message:{' '}
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
style={{ width: 320 }}
/>
</label>
</div>
<div style={{ marginBottom: 12 }}>
<button type="button" onClick={() => setSubmittedMessage(message)}>
Start HTTP stream
</button>
<button type="button" onClick={() => setSubmittedMessage(undefined)} style={{ marginLeft: 8 }}>
Reset
</button>
</div>
<div className="terminal-window">
{stream && stream.length > 0 ? String(stream) : (
<span className="terminal-generating">
{submittedMessage === undefined ? 'Waiting for input…' : 'Generating...'}
</span>
)}
</div>
</>
)
}
Loading
Loading