use-next-sse 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  # use-next-sse
2
+ [![Node.js Package](https://github.com/alexanderkasten/use-next-sse/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/alexanderkasten/use-next-sse/actions/workflows/npm-publish.yml)
3
+
2
4
 
3
5
  use-next-sse is a lightweight and easy-to-use React hook library for implementing Server-Sent Events (SSE) in Next.js applications, enabling real-time, unidirectional data streaming from server to client.
4
6
 
@@ -18,7 +20,7 @@ Create a new file `app/api/sse/route.ts` with the following content:
18
20
  import { createSSEHandler } from 'use-next-sse';
19
21
 
20
22
  export const dynamic = 'force-dynamic';
21
- export const GET = createSSEHandler(async (send, close) => {
23
+ export const GET = createSSEHandler((send, close) => {
22
24
  let count = 0;
23
25
  const interval = setInterval(() => {
24
26
  send({ count: count++ }, 'counter');
@@ -1,4 +1,4 @@
1
- import { NextRequest } from 'next/server';
1
+ import type { NextRequest } from 'next/server';
2
2
  /**
3
3
  * A function that sends data to the client.
4
4
  * @param data - The data to send to the client.
@@ -9,12 +9,22 @@ import { NextRequest } from 'next/server';
9
9
  * send({ message: 'test' }, 'testEvent');
10
10
  */
11
11
  type SendFunction = (data: any, eventName?: string) => void;
12
+ /**
13
+ * A function that cleans up resources when the connection is closed.
14
+ * @returns void | Promise<void>
15
+ * @example
16
+ * return () => {
17
+ * console.log('Cleanup');
18
+ * };
19
+ */
20
+ type Destructor = (() => void) | Promise<() => void> | (() => Promise<void>);
12
21
  /**
13
22
  * A function that handles the Server-Sent Events (SSE) connection.
14
23
  * @param send - A function to send data to the client.
15
24
  * @param close - A function to close the SSE connection.
16
25
  * @returns void | Promise<void> | (() => void)
17
26
  * The callback can return a cleanup function that will be called when the connection is closed.
27
+ * **HINT:** See also {@link Destructor}, the createSSEHandler {@link SSEOptions} and {@link SSECallback} context `onClose`.
18
28
  */
19
29
  type SSECallback = (send: SendFunction,
20
30
  /**
@@ -27,12 +37,23 @@ close: () => void,
27
37
  /**
28
38
  * An object containing the last event ID received by the client. Not null if the client has been reconnected.
29
39
  * @property lastEventId - The last event ID from the client.
40
+ * @property onClose - A function to set a cleanup function that will be called when the connection is closed. If the cleanup function is already set, a warning will be logged. The cleanup function set by the `onClose` function will be called even if the cleanup function returned by the callback is not called.
30
41
  * @example
31
42
  * { lastEventId: '12345' }
32
43
  */
33
44
  context: {
34
45
  lastEventId: string | null;
35
- }) => void | Promise<void> | (() => void);
46
+ onClose: (destructor: Destructor) => void;
47
+ }) => void | Promise<void> | Destructor;
48
+ /**
49
+ * An optional object to configure the Server-Sent Events (SSE) handler.
50
+ * @property onClose - A function that will be called when the connection is closed even if the SSECallback has not been called yet.
51
+ * @example
52
+ * { onClose: () => console.log('SSE connection has been closed and cleaned up.') }
53
+ */
54
+ type SSEOptions = {
55
+ onClose?: Destructor;
56
+ };
36
57
  /**
37
58
  * Creates a Server-Sent Events (SSE) handler for Next.js.
38
59
  *
@@ -42,6 +63,22 @@ context: {
42
63
  * - `context`: An object containing the last event ID received by the client. Not null if the client has been reconnected.
43
64
  * The callback can return a cleanup function that will be called when the connection is closed.
44
65
  *
66
+ * **HINT:**
67
+ * Be sure to **NOT** await long running operations in the callback, as this will block the response from being sent initially to the client. Instead wrap your long running operations in a async function to allow the response to be sent to the client first.
68
+ *
69
+ * @example
70
+ ```
71
+ export const GET = createSSEHandler((send, close) => {
72
+ const asyncStuff = async () => {
73
+ // async
74
+ };
75
+
76
+ asyncStuff();
77
+ });
78
+ ```
79
+ *
80
+ * @param options - An optional object to configure the handler. {@link SSEOptions}
81
+ * - `onClose`: A function that will be called when the connection is closed even if the SSECallback has not been called yet.
45
82
  * @returns A function that handles the SSE request. This function takes a `NextRequest` object as an argument.
46
83
  *
47
84
  * The returned function creates a `ReadableStream` to handle the SSE connection. It sets up the `send` and `close` functions,
@@ -52,5 +89,5 @@ context: {
52
89
  * - `Cache-Control`: `no-cache, no-transform`
53
90
  * - `Connection`: `keep-alive`
54
91
  */
55
- export declare function createSSEHandler(callback: SSECallback): (request: NextRequest) => Promise<Response>;
92
+ export declare function createSSEHandler(callback: SSECallback, options?: SSEOptions): (request: NextRequest) => Promise<Response>;
56
93
  export {};
@@ -10,6 +10,22 @@ exports.createSSEHandler = void 0;
10
10
  * - `context`: An object containing the last event ID received by the client. Not null if the client has been reconnected.
11
11
  * The callback can return a cleanup function that will be called when the connection is closed.
12
12
  *
13
+ * **HINT:**
14
+ * Be sure to **NOT** await long running operations in the callback, as this will block the response from being sent initially to the client. Instead wrap your long running operations in a async function to allow the response to be sent to the client first.
15
+ *
16
+ * @example
17
+ ```
18
+ export const GET = createSSEHandler((send, close) => {
19
+ const asyncStuff = async () => {
20
+ // async
21
+ };
22
+
23
+ asyncStuff();
24
+ });
25
+ ```
26
+ *
27
+ * @param options - An optional object to configure the handler. {@link SSEOptions}
28
+ * - `onClose`: A function that will be called when the connection is closed even if the SSECallback has not been called yet.
13
29
  * @returns A function that handles the SSE request. This function takes a `NextRequest` object as an argument.
14
30
  *
15
31
  * The returned function creates a `ReadableStream` to handle the SSE connection. It sets up the `send` and `close` functions,
@@ -20,14 +36,40 @@ exports.createSSEHandler = void 0;
20
36
  * - `Cache-Control`: `no-cache, no-transform`
21
37
  * - `Connection`: `keep-alive`
22
38
  */
23
- function createSSEHandler(callback) {
39
+ function createSSEHandler(callback, options) {
24
40
  return async function (request) {
25
41
  const encoder = new TextEncoder();
26
42
  let isClosed = false;
27
- let cleanup;
43
+ let cleanup = options === null || options === void 0 ? void 0 : options.onClose;
44
+ let cleanupSetBy = (options === null || options === void 0 ? void 0 : options.onClose) ? '`onClose` through createSSEHandler options' : '';
28
45
  let messageId = 0;
46
+ let controller;
47
+ function onClose(destructor) {
48
+ if (cleanup != null) {
49
+ if (!isClosed) {
50
+ logAlreadySetWarning(cleanupSetBy);
51
+ }
52
+ }
53
+ else {
54
+ cleanup = destructor;
55
+ cleanupSetBy = '`onClose` through SSECallback context';
56
+ }
57
+ }
58
+ function close() {
59
+ if (!isClosed) {
60
+ isClosed = true;
61
+ controller === null || controller === void 0 ? void 0 : controller.close();
62
+ if (typeof cleanup === 'function') {
63
+ cleanup();
64
+ }
65
+ }
66
+ }
67
+ request.signal.addEventListener('abort', () => {
68
+ close();
69
+ });
29
70
  const stream = new ReadableStream({
30
- start(controller) {
71
+ async start(cntrl) {
72
+ controller = cntrl;
31
73
  const send = (data, eventName) => {
32
74
  if (!isClosed) {
33
75
  let message = `id: ${messageId}\n`;
@@ -35,26 +77,21 @@ function createSSEHandler(callback) {
35
77
  message += `event: ${eventName}\n`;
36
78
  }
37
79
  message += `data: ${JSON.stringify(data)}\n\n`;
38
- controller.enqueue(encoder.encode(message));
80
+ cntrl.enqueue(encoder.encode(message));
39
81
  messageId++;
40
82
  }
41
83
  };
42
- function close() {
43
- if (!isClosed) {
44
- isClosed = true;
45
- controller.close();
46
- if (typeof cleanup === 'function') {
47
- cleanup();
84
+ const result = await callback(send, close, { lastEventId: request.headers.get('Last-Event-ID'), onClose });
85
+ if (typeof result === 'function') {
86
+ if (cleanup != null) {
87
+ if (!isClosed) {
88
+ logAlreadySetWarning(cleanupSetBy);
48
89
  }
49
90
  }
91
+ else {
92
+ cleanup = result;
93
+ }
50
94
  }
51
- const result = callback(send, close, { lastEventId: request.headers.get('Last-Event-ID') });
52
- if (typeof result === 'function') {
53
- cleanup = result;
54
- }
55
- request.signal.addEventListener('abort', () => {
56
- close();
57
- });
58
95
  },
59
96
  });
60
97
  return new Response(stream, {
@@ -67,3 +104,10 @@ function createSSEHandler(callback) {
67
104
  };
68
105
  }
69
106
  exports.createSSEHandler = createSSEHandler;
107
+ let warningLogged = false;
108
+ function logAlreadySetWarning(cleanupSetBy) {
109
+ if (!warningLogged) {
110
+ console.warn(`[use-next-sse:createSSEHandler]:\tCleanup function already set by ${cleanupSetBy}. Ignoring new cleanup function.`);
111
+ warningLogged = true;
112
+ }
113
+ }
@@ -1,4 +1,4 @@
1
- import { NextRequest } from 'next/server';
1
+ import type { NextRequest } from 'next/server';
2
2
  /**
3
3
  * A function that sends data to the client.
4
4
  * @param data - The data to send to the client.
@@ -9,12 +9,22 @@ import { NextRequest } from 'next/server';
9
9
  * send({ message: 'test' }, 'testEvent');
10
10
  */
11
11
  type SendFunction = (data: any, eventName?: string) => void;
12
+ /**
13
+ * A function that cleans up resources when the connection is closed.
14
+ * @returns void | Promise<void>
15
+ * @example
16
+ * return () => {
17
+ * console.log('Cleanup');
18
+ * };
19
+ */
20
+ type Destructor = (() => void) | Promise<() => void> | (() => Promise<void>);
12
21
  /**
13
22
  * A function that handles the Server-Sent Events (SSE) connection.
14
23
  * @param send - A function to send data to the client.
15
24
  * @param close - A function to close the SSE connection.
16
25
  * @returns void | Promise<void> | (() => void)
17
26
  * The callback can return a cleanup function that will be called when the connection is closed.
27
+ * **HINT:** See also {@link Destructor}, the createSSEHandler {@link SSEOptions} and {@link SSECallback} context `onClose`.
18
28
  */
19
29
  type SSECallback = (send: SendFunction,
20
30
  /**
@@ -27,12 +37,23 @@ close: () => void,
27
37
  /**
28
38
  * An object containing the last event ID received by the client. Not null if the client has been reconnected.
29
39
  * @property lastEventId - The last event ID from the client.
40
+ * @property onClose - A function to set a cleanup function that will be called when the connection is closed. If the cleanup function is already set, a warning will be logged. The cleanup function set by the `onClose` function will be called even if the cleanup function returned by the callback is not called.
30
41
  * @example
31
42
  * { lastEventId: '12345' }
32
43
  */
33
44
  context: {
34
45
  lastEventId: string | null;
35
- }) => void | Promise<void> | (() => void);
46
+ onClose: (destructor: Destructor) => void;
47
+ }) => void | Promise<void> | Destructor;
48
+ /**
49
+ * An optional object to configure the Server-Sent Events (SSE) handler.
50
+ * @property onClose - A function that will be called when the connection is closed even if the SSECallback has not been called yet.
51
+ * @example
52
+ * { onClose: () => console.log('SSE connection has been closed and cleaned up.') }
53
+ */
54
+ type SSEOptions = {
55
+ onClose?: Destructor;
56
+ };
36
57
  /**
37
58
  * Creates a Server-Sent Events (SSE) handler for Next.js.
38
59
  *
@@ -42,6 +63,22 @@ context: {
42
63
  * - `context`: An object containing the last event ID received by the client. Not null if the client has been reconnected.
43
64
  * The callback can return a cleanup function that will be called when the connection is closed.
44
65
  *
66
+ * **HINT:**
67
+ * Be sure to **NOT** await long running operations in the callback, as this will block the response from being sent initially to the client. Instead wrap your long running operations in a async function to allow the response to be sent to the client first.
68
+ *
69
+ * @example
70
+ ```
71
+ export const GET = createSSEHandler((send, close) => {
72
+ const asyncStuff = async () => {
73
+ // async
74
+ };
75
+
76
+ asyncStuff();
77
+ });
78
+ ```
79
+ *
80
+ * @param options - An optional object to configure the handler. {@link SSEOptions}
81
+ * - `onClose`: A function that will be called when the connection is closed even if the SSECallback has not been called yet.
45
82
  * @returns A function that handles the SSE request. This function takes a `NextRequest` object as an argument.
46
83
  *
47
84
  * The returned function creates a `ReadableStream` to handle the SSE connection. It sets up the `send` and `close` functions,
@@ -52,5 +89,5 @@ context: {
52
89
  * - `Cache-Control`: `no-cache, no-transform`
53
90
  * - `Connection`: `keep-alive`
54
91
  */
55
- export declare function createSSEHandler(callback: SSECallback): (request: NextRequest) => Promise<Response>;
92
+ export declare function createSSEHandler(callback: SSECallback, options?: SSEOptions): (request: NextRequest) => Promise<Response>;
56
93
  export {};
@@ -7,6 +7,22 @@
7
7
  * - `context`: An object containing the last event ID received by the client. Not null if the client has been reconnected.
8
8
  * The callback can return a cleanup function that will be called when the connection is closed.
9
9
  *
10
+ * **HINT:**
11
+ * Be sure to **NOT** await long running operations in the callback, as this will block the response from being sent initially to the client. Instead wrap your long running operations in a async function to allow the response to be sent to the client first.
12
+ *
13
+ * @example
14
+ ```
15
+ export const GET = createSSEHandler((send, close) => {
16
+ const asyncStuff = async () => {
17
+ // async
18
+ };
19
+
20
+ asyncStuff();
21
+ });
22
+ ```
23
+ *
24
+ * @param options - An optional object to configure the handler. {@link SSEOptions}
25
+ * - `onClose`: A function that will be called when the connection is closed even if the SSECallback has not been called yet.
10
26
  * @returns A function that handles the SSE request. This function takes a `NextRequest` object as an argument.
11
27
  *
12
28
  * The returned function creates a `ReadableStream` to handle the SSE connection. It sets up the `send` and `close` functions,
@@ -17,14 +33,40 @@
17
33
  * - `Cache-Control`: `no-cache, no-transform`
18
34
  * - `Connection`: `keep-alive`
19
35
  */
20
- export function createSSEHandler(callback) {
36
+ export function createSSEHandler(callback, options) {
21
37
  return async function (request) {
22
38
  const encoder = new TextEncoder();
23
39
  let isClosed = false;
24
- let cleanup;
40
+ let cleanup = options === null || options === void 0 ? void 0 : options.onClose;
41
+ let cleanupSetBy = (options === null || options === void 0 ? void 0 : options.onClose) ? '`onClose` through createSSEHandler options' : '';
25
42
  let messageId = 0;
43
+ let controller;
44
+ function onClose(destructor) {
45
+ if (cleanup != null) {
46
+ if (!isClosed) {
47
+ logAlreadySetWarning(cleanupSetBy);
48
+ }
49
+ }
50
+ else {
51
+ cleanup = destructor;
52
+ cleanupSetBy = '`onClose` through SSECallback context';
53
+ }
54
+ }
55
+ function close() {
56
+ if (!isClosed) {
57
+ isClosed = true;
58
+ controller === null || controller === void 0 ? void 0 : controller.close();
59
+ if (typeof cleanup === 'function') {
60
+ cleanup();
61
+ }
62
+ }
63
+ }
64
+ request.signal.addEventListener('abort', () => {
65
+ close();
66
+ });
26
67
  const stream = new ReadableStream({
27
- start(controller) {
68
+ async start(cntrl) {
69
+ controller = cntrl;
28
70
  const send = (data, eventName) => {
29
71
  if (!isClosed) {
30
72
  let message = `id: ${messageId}\n`;
@@ -32,26 +74,21 @@ export function createSSEHandler(callback) {
32
74
  message += `event: ${eventName}\n`;
33
75
  }
34
76
  message += `data: ${JSON.stringify(data)}\n\n`;
35
- controller.enqueue(encoder.encode(message));
77
+ cntrl.enqueue(encoder.encode(message));
36
78
  messageId++;
37
79
  }
38
80
  };
39
- function close() {
40
- if (!isClosed) {
41
- isClosed = true;
42
- controller.close();
43
- if (typeof cleanup === 'function') {
44
- cleanup();
81
+ const result = await callback(send, close, { lastEventId: request.headers.get('Last-Event-ID'), onClose });
82
+ if (typeof result === 'function') {
83
+ if (cleanup != null) {
84
+ if (!isClosed) {
85
+ logAlreadySetWarning(cleanupSetBy);
45
86
  }
46
87
  }
88
+ else {
89
+ cleanup = result;
90
+ }
47
91
  }
48
- const result = callback(send, close, { lastEventId: request.headers.get('Last-Event-ID') });
49
- if (typeof result === 'function') {
50
- cleanup = result;
51
- }
52
- request.signal.addEventListener('abort', () => {
53
- close();
54
- });
55
92
  },
56
93
  });
57
94
  return new Response(stream, {
@@ -63,3 +100,10 @@ export function createSSEHandler(callback) {
63
100
  });
64
101
  };
65
102
  }
103
+ let warningLogged = false;
104
+ function logAlreadySetWarning(cleanupSetBy) {
105
+ if (!warningLogged) {
106
+ console.warn(`[use-next-sse:createSSEHandler]:\tCleanup function already set by ${cleanupSetBy}. Ignoring new cleanup function.`);
107
+ warningLogged = true;
108
+ }
109
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-next-sse",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "A lightweight Server-Sent Events (SSE) library for Next.js, enabling real-time, unidirectional data streaming from server to client",
5
5
  "main": "dist/cjs/index.js",
6
6
  "types": "dist/index.d.ts",