nestia icon indicating copy to clipboard operation
nestia copied to clipboard

Support for Sse decorator

Open ninthsun91 opened this issue 1 year ago • 5 comments

TypedRoute is not supporting Sse decorator from @nestjs/common, thus cannot use Nestia for SSE endpoints.

For example, for the code something like below,

// original decorator used
// @Sse('/path/to')
// Nestia decorator used
@TypedRoute.Get('/path/to')
async waitQueue(
  @TypedQuery('id') queueId: string,
  @Req() req: Request,
) {
  const isCompleted = new Subject<void>();
  return interval(3000).pipe(
    takeUntil(isCompleted),
    mergeMap(async () => {
      // do stuffs
    }),
  );
}

if I use @TypedRoute.Get in place of @Sse, this endpoint will work as a normal GET method.

Could you please add @Sse decorator to TypedRoute, or is there any work around for SSE endpoints?

ninthsun91 avatar Jun 14 '23 17:06 ninthsun91

Do you have a good idea that how SDK should be generated?

If you have insight about it, can you write an example SDK code about that?

samchon avatar Jun 15 '23 18:06 samchon

Honestly, neither do I know how Nestia generates sdk internally yet, nor am I skilled enough to contribute to your project. So, if you think this issue is not part of your primary goals, it's fine to close the issue.

Maybe I'll re-open this issue when I get better understanding to this project and become ready to contribute.

ninthsun91 avatar Jun 16 '23 08:06 ninthsun91

I mean just interface design goal of SDK what you want. Don't be burdened please.

samchon avatar Jun 16 '23 08:06 samchon

Sure.

Would this be what you are expecting?

/**
 * EventSource constructor:
 * new(url: string | URL, eventSourceInitDict?: EventSourceInit): EventSource;
 * 
 * interface EventSourceInit {
 *   withCredentials?: boolean;
 * }
 */
interface EventSourceConnection {
  url: string;
  withCredentials?: boolean;
}
interface EventSourceListener {
  /**
   * EventSource event type
   * pre-built types: 'message' | 'open' | 'error'
   * but, can be any string
   */
  type: string;

  /**
   * interface MessageEvent<T = any> extends Event {
   *  readonly data: T;
   * }
   * 
   * I'm not sure whether other properties are used.
   */
  listener: (this: EventSource, event: MessageEvent) => any;

  /**
   * Indicates whether the EventSource connection should be closed after the event.
   * 
   * For example, if true,
   * the listner should be wrapped with eventSource.close().
   * 
   * false, by default.
   */
  close?: boolean;
}

API.path.to.methodName(connection: EventSourceConnection, ...listener: EventSourceListener[])

Just wrote it similar to how normal requests are handled. So, real use case might look like this.

API.path.to.methodName({
  url: 'http://localhost:3000',
}, {
  type: 'message',
  listener: messageHandler,
}, {
  type: 'error',
  listener: errorHandler,
  close: true,
}, {
  type: 'complete',
  listener: completeHandler,
  close: true,
});

or maybe, the interface can return the EventSource instance, so that the user can add listeners.

*edit

const eventSource = API.path.to.methodName({ url: 'http://localhost:3000' })
eventSource.addEventListener('message', messageHandler);
eventSource.addEventListener('error', errorHandler);
eventSource.addEventListener('complete', completeHandler);

ninthsun91 avatar Jun 16 '23 09:06 ninthsun91

Hallo @samchon Thank you for the amazing work. I've face the same struggle as described in this issue. I just ended up using https://github.com/Azure/fetch-event-source instead of the sdk for this particular SSE endpoint.

Also thinking about how it can be integrate in nestia:

  1. If you don't mind integrating fetch-event-source as peer-dependency in nestia fetcher. Then the workflow will be like this.
a. nestjs enpoint has sse decorator ?
b. use SseFetcher ( leverage fetchEventSource from fetch-event-source-package )
c. wrap onmessage, onerror, onopen and onclose in Promise.

I've tested this approch with react-native client and it work fine.

  1. Another option would be really just to generate something like this for SSE marked endpoint. and repeat the option 2.c
const eventSource = new EventSource('/sse');
eventSource.onmessage = ({ data }) => {
  console.log('New message', JSON.parse(data));
};

Again thank you for your amazing work.

Cheers.

dev-dafab avatar Apr 02 '24 12:04 dev-dafab