restart
This commit is contained in:
@@ -8,6 +8,7 @@ import { loggerMiddleware } from './middleware/logger.js';
|
|||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
import { authMiddleware } from './middleware/auth.js';
|
import { authMiddleware } from './middleware/auth.js';
|
||||||
import { ensureConnection } from '../db/store.js';
|
import { ensureConnection } from '../db/store.js';
|
||||||
|
import { closeAll as closeAllSse } from './sse.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@@ -133,8 +134,12 @@ export async function startApi() {
|
|||||||
|
|
||||||
export async function stopApi() {
|
export async function stopApi() {
|
||||||
if (!server) return;
|
if (!server) return;
|
||||||
|
// Long-lived SSE (and keep-alive) sockets otherwise block server.close()
|
||||||
|
// forever — node --watch hangs on "Waiting for graceful termination...".
|
||||||
|
closeAllSse();
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
server.close((err) => (err ? reject(err) : resolve()));
|
server.close((err) => (err ? reject(err) : resolve()));
|
||||||
|
server.closeAllConnections();
|
||||||
});
|
});
|
||||||
server = undefined;
|
server = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* after a successful mutation so other tabs/users can refetch.
|
* after a successful mutation so other tabs/users can refetch.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {{ res: import('express').Response, companyId: number, userId: number }} SseClient */
|
/** @typedef {{ res: import('express').Response, companyId: number, userId: number, keepAlive: NodeJS.Timeout }} SseClient */
|
||||||
|
|
||||||
/** @type {Set<SseClient>} */
|
/** @type {Set<SseClient>} */
|
||||||
const clients = new Set();
|
const clients = new Set();
|
||||||
@@ -18,16 +18,19 @@ const KEEP_ALIVE_MS = 25_000;
|
|||||||
*/
|
*/
|
||||||
export function subscribe(res, { companyId, userId }) {
|
export function subscribe(res, { companyId, userId }) {
|
||||||
/** @type {SseClient} */
|
/** @type {SseClient} */
|
||||||
const client = { res, companyId, userId };
|
const client = {
|
||||||
|
res,
|
||||||
|
companyId,
|
||||||
|
userId,
|
||||||
|
keepAlive: setInterval(() => {
|
||||||
|
if (res.writableEnded) return;
|
||||||
|
res.write(': keepalive\n\n');
|
||||||
|
}, KEEP_ALIVE_MS),
|
||||||
|
};
|
||||||
clients.add(client);
|
clients.add(client);
|
||||||
|
|
||||||
const keepAlive = setInterval(() => {
|
|
||||||
if (res.writableEnded) return;
|
|
||||||
res.write(': keepalive\n\n');
|
|
||||||
}, KEEP_ALIVE_MS);
|
|
||||||
|
|
||||||
const unsubscribe = () => {
|
const unsubscribe = () => {
|
||||||
clearInterval(keepAlive);
|
clearInterval(client.keepAlive);
|
||||||
clients.delete(client);
|
clients.delete(client);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,6 +38,20 @@ export function subscribe(res, { companyId, userId }) {
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End every open SSE stream so `server.close()` can finish (e.g. node --watch).
|
||||||
|
* EventSource clients reconnect to the new process.
|
||||||
|
*/
|
||||||
|
export function closeAll() {
|
||||||
|
for (const client of [...clients]) {
|
||||||
|
clearInterval(client.keepAlive);
|
||||||
|
clients.delete(client);
|
||||||
|
if (!client.res.writableEnded) {
|
||||||
|
client.res.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast a named signal to subscribers of the given company.
|
* Broadcast a named signal to subscribers of the given company.
|
||||||
* @param {string} signal - SSE event name (e.g. `category`)
|
* @param {string} signal - SSE event name (e.g. `category`)
|
||||||
|
|||||||
Reference in New Issue
Block a user