river.ts

Easy, Composable, and type-safe Server-Sent Events (SSE)

README

00171-1636846244

🌊 river.ts | ✨ Composable, Typesafe SSE Events


river.ts is a Based library for handling server-sent events (SSE) in TypeScript. It allows you to build a common interface for events, then call it from one place Both on server and client.
Currently compatible with express-like backends.

🌟 Features

- 💡 Easy-to-use API for subscribing to and handling events
- 🔄 Automatic reconnection with configurable delay
- 🔌 Works with GET & Other HTTP Methods along with custom headers, body, etc...
- 🛠️ Event listeners for events, with typesafe event handlers.

📦 Installation

Bun

  1. ```bash
  2. bun install river.ts
  3. ```

NPM (why tho)

  1. ```bash
  2. npm install river.ts
  3. ```

🚀 Usage

🏗 Build your event map

Chain commands together to build a map of events, you can add the types as type arguments or function arguments.
  1. ```typescript
  2. import { RiverEvents } from 'river.ts';

  3. const events = new RiverEvents()
  4. .map_event("ping", {
  5.   message: "pong",
  6. })
  7. .map_event("payload", {
  8.   data: [
  9.    { id: 1, name: "Alice" },
  10.    { id: 2, name: "Bob" },
  11.   ],
  12. }).build()
  13. ```

🌠 On the Server

  1. ```typescript
  2. import { RiverEmitter } from 'river.ts/server';
  3. import {events} from './events';

  4. // init the server
  5. const server = RiverEmitter.init(events)

  6. // Then, use .stream() as body init it using the `Response` object of your framework
  7. function GET(req: Request) {
  8. return new Response(
  9.   server.stream((emitter) => {
  10.    // do stuff
  11.    // emit simple text message
  12.    emitter.emit_event("ping", { message: "pong" });

  13.    // do more stuff
  14.    // emit complex json data
  15.    emitter.emit_event("payload", {
  16.     // type safe data
  17.     data: [
  18.      { id: 1, name: "Alice" },
  19.      { id: 2, name: "Bob" },
  20.     ],
  21.    });
  22.   }),
  23.   {
  24.    // convenience method to set headers for text/event-stream
  25.    headers:
  26.     server.headers(
  27.      // optional, set your headers
  28.     ),
  29.   },
  30. );
  31. }
  32. ```

🚀 On the client

  1. ```typescript
  2. import { RiverClient } from 'river.ts/client';
  3. import {events} from './events';

  4. // On the client
  5. const client = RiverClient.init(events)

  6. await client
  7. // add url, method, headers, etc (GET/POST/Etc, all work)
  8. .prepare("http://localhost:3000/events", {
  9.   // custom headers
  10.   method: "POST",
  11.   body: JSON.stringify({}),
  12. })
  13. // add event listeners
  14. .on("ping", (res) => {
  15.   console.log("on data", res);
  16.   // typeof res
  17.   // {
  18.   //  message: string;
  19.   //  type: "ping";
  20.   // }
  21. })
  22. // add more event listeners
  23. .on("payload", (res) => {
  24.   console.log("on data", res);
  25.   // typeof res
  26.   // {
  27.   //  data: {
  28.   //   id: number;
  29.   //   name: string;
  30.   //  }[];
  31.   //  type: "payload";
  32.   // };
  33.   if (!res.data) {
  34.    // you can close it anytime if you assign it to a constant beforehand
  35.    client.close();
  36.   }
  37. })
  38. // start the stream
  39. .stream();
  40. ```

🔍 Type Safety

After building the event map, You can either use typeof events.{event} or use the type InferEventType with the event name
  1. ```typescript
  2. import { InferEventType } from 'river.ts';

  3. const events = ....build()
  4. type Events = typeof events
  5. type PingEvent = InferEventType<Events, "ping">;
  6. // {
  7. //  message: string;
  8. //  type: "ping";
  9. // }

  10. const events: PingEvent[] = []

  11. // then you can push to it if you want and the types will be ok
  12. events.push({
  13. message: "pong",
  14. type: "ping",
  15. })
  16. ```

🎉 Contributing

Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request.

📄 License

Don't be a bozo