better-sse
better-sse copied to clipboard
Add adapters to enable compatibility with any runtime and/or framework
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:
- Node - HTTP/1.1
- Node - HTTP/2 Compatibility API
- Node - HTTP/2 Core API
- Deno - HTTP
- Bun - HTTP
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;
}