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

Support Fetch API Request and Response #79

Open MatthewWid opened 2 weeks ago

MatthewWid commented 2 weeks ago

Update the Session to be able to take in a Request object from the Fetch API and return in some way a Response object.

Web streams and the Fetch API appear to be the predominant standard with JavaScript moving forward, and as such this will enable compatibility with the many libraries (#70, #78) that expose Request/Response interfaces rather than the Node IncomingMessage and ServerResponse objects.


The new API would simply add another overload to the session constructor, so you could now do one of:

createSession(IncomingMessage, ServerResponse, options)
createSession(Http2ServerRequest, Http2ServerResponse, options)
createSession(Request, options)

Which would be used like the following:

import { createSession } from "better-sse";

const session = await createSession(request);

return session.getResponse();

If the session detects that it is passed a Request object it will defer calling the internal initialize method - which flushes the response head and sends preamble data - until the getResponse method has been called.

When it is finally invoked, a new Response object with a ReadableStream as its body will be returned, given the parameters from the session constructor options argument as well as any additional parameters given to the method itself which are passed through to the Response constructor, and the library will begin streaming data through it.

This may involve a refactor to where, even the if the IncomingMessage/ServerResponse objects are given, they are converted to be Request/Response objects internally so that a single, standardized interface can be used across different frameworks and runtimes rather than repeatedly running down conditional trees with slow and janky instanceof checks.


This may obsolete #77 as instead of making adapters implement a now-abstract Session class you would instead write an adapter to fit your runtime/frameworks lifecycle into the standard Request/Response objects, requiring no breaking changes to the current package API and overall being a much cleaner implementation.

go4cas commented 1 week ago

@MatthewWid ... first off, thanks for the great library!

Question: once this issue has been implemented, will better-sse work in a Hono NodeJS app?

MatthewWid commented 4 days ago

@go4cas yes, that would be the case.

In theory you should even be able to use Hono with non-Node runtimes such as Deno and Bun, but I need to do more research and testing on that.


I don't have a fully formed idea of what the API would and could look like at the moment, but something resembling this is the general workflow:

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

  // register to channels and/or push events

  return session.getResponse();
});

There might be some strangeness, however, with the order of things being returned whereby data is pushed to the session before the response is returned and headers are flushed. In which case, there needs to be some mechanism where we can access the session only after the response has been returned. For example:

const session = await createSession(c.req); // flushing headers is deferred until `getResponse` is called

session.once("connected", () => {
  // safe to send data
});

return session.getResponse();

or, mimicking how hono/streaming looks:

return (await createSession(c.req)).getResponse((session) => {
  // safe to send data
});

I'll have to give it more thought when I can find the time.