MatthewWid / better-sse

⬆ Dead simple, dependency-less, spec-compliant server-sent events implementation for Node, written in TypeScript.
https://matthewwid.github.io/better-sse/
MIT License
558 stars 14 forks source link

Add adapters to enable compatibility with any runtime and/or framework #77

Open MatthewWid opened 3 weeks ago

MatthewWid commented 3 weeks ago

Introduce the concept of adapters, allowing sessions to be instantiated with inputs other than the Node HTTP request and response objects and enabling compatibility with practically any JavaScript/TypeScript library, including non-Node runtimes such as Bun and Deno.

To implement this, a breaking change is necessary whereby Session is updated to be an abstract class that is then implemented by concrete classes for whichever runtime and/or framework is needed.


The better-sse package will export a few officially supported adapters:

Others can be community-made in separate packages or implemented by the user themselves if they have a use-case requiring highly specific customisation.

To use adapters you would simply import the specific module you need and then use the library like normal:

// Exports Node HTTP/1.1 by default
import { createSession } from "better-sse";
// Equivalent to
import { createSession } from "better-sse/adapters/node/http";

// Adapters are under a different entrypoint
import { createSession } from "better-sse/adapters/node/http2-compat";
import { createSession } from "better-sse/adapters/node/http2-core";
import { createSession } from "better-sse/adapters/deno/http";
import { createSession } from "better-sse/adapters/bun/http";

Everything else (channels, event buffers, etc.) would still all function the exact same. Only the session - which forms the actual interface between the business code and the network transmissions - would need to be updated.


Community-made adapters would also be available. An example of using a Hono adapter (#70) would be:

import { Hono } from "hono";
import { createChannel } from "better-sse";
import { createSession } from "better-sse-adapter-hono";

const app = new Hono();

const channel = createChannel();

app.get("/sse", async (c) => {
  const session = await createSession(c);

  channel.register(session);
});

export default app;

The current idea is that adapters would need to implement five methods - three that retrieve headers and parameters from the request, one that sends the response head and one to send raw data (chunks) over the wire:

abstract getDefaultHeaders(): OutgoingHttpHeaders;
abstract getHeader(name: string): string | undefined;
abstract getParam(name: string): string | undefined;
abstract sendHead(statusCode: number, headers: OutgoingHttpHeaders): void;
abstract sendChunk(chunk: string): void;

This might need more thought, however, as certain frameworks such as Next.js and hapi (#76) instead rely on returning an object back to the method handler to send the response head rather than invoking a function.

Perhaps for those specific adapters they would have a Response instance property that could be returned and sendHead would instead be no-op'd:

async (context) => {
  const session = await createSession(context);

  return session.Response;
}