133 lines
3.1 KiB
JavaScript
133 lines
3.1 KiB
JavaScript
const http = require("node:http");
|
|
const { Server: SocketServer } = require("socket.io");
|
|
const { createApp } = require("./app");
|
|
const { openDatabase } = require("./database");
|
|
const { runMigrations } = require("./database/migrate");
|
|
|
|
function listen(httpServer, port, host) {
|
|
return new Promise((resolve, reject) => {
|
|
function onError(error) {
|
|
httpServer.off("listening", onListening);
|
|
reject(error);
|
|
}
|
|
function onListening() {
|
|
httpServer.off("error", onError);
|
|
resolve(httpServer.address());
|
|
}
|
|
httpServer.once("error", onError);
|
|
httpServer.once("listening", onListening);
|
|
httpServer.listen(port, host);
|
|
});
|
|
}
|
|
|
|
function createRuntime({ config, logger = console }) {
|
|
const database = openDatabase(config.database);
|
|
let migrations;
|
|
try {
|
|
migrations = runMigrations(database);
|
|
} catch (error) {
|
|
database.close();
|
|
throw error;
|
|
}
|
|
|
|
const lifecycle = {
|
|
ready: false,
|
|
stopping: false,
|
|
};
|
|
const app = createApp({ config, database, lifecycle, migrations });
|
|
const httpServer = http.createServer(app);
|
|
const io = new SocketServer(httpServer, {
|
|
serveClient: false,
|
|
});
|
|
|
|
io.on("connection", (socket) => {
|
|
logger.info?.(`Socket.IO client connected: ${socket.id}`);
|
|
socket.on("disconnect", () => {
|
|
logger.info?.(`Socket.IO client disconnected: ${socket.id}`);
|
|
});
|
|
});
|
|
|
|
let address = null;
|
|
let startPromise = null;
|
|
let stopPromise = null;
|
|
|
|
async function start() {
|
|
if (stopPromise) {
|
|
throw new Error("Cannot start a runtime after shutdown has begun");
|
|
}
|
|
if (startPromise) {
|
|
return startPromise;
|
|
}
|
|
|
|
startPromise = listen(httpServer, config.port, config.host)
|
|
.then((listeningAddress) => {
|
|
address = listeningAddress;
|
|
lifecycle.ready = true;
|
|
return listeningAddress;
|
|
})
|
|
.catch((error) => {
|
|
database.close();
|
|
throw error;
|
|
});
|
|
return startPromise;
|
|
}
|
|
|
|
async function stop(reason = "shutdown") {
|
|
if (stopPromise) {
|
|
return stopPromise;
|
|
}
|
|
|
|
lifecycle.ready = false;
|
|
lifecycle.stopping = true;
|
|
stopPromise = new Promise((resolve) => {
|
|
let settled = false;
|
|
const finish = () => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timeout);
|
|
if (database.open) {
|
|
database.close();
|
|
}
|
|
resolve();
|
|
};
|
|
const timeout = setTimeout(() => {
|
|
logger.warn?.(
|
|
`Graceful shutdown timed out after ${config.shutdownTimeoutMs}ms`,
|
|
);
|
|
httpServer.closeAllConnections?.();
|
|
finish();
|
|
}, config.shutdownTimeoutMs);
|
|
timeout.unref?.();
|
|
|
|
if (!httpServer.listening) {
|
|
io.close();
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
logger.info?.(`Stopping Announcements API (${reason})`);
|
|
io.close(finish);
|
|
});
|
|
return stopPromise;
|
|
}
|
|
|
|
return {
|
|
app,
|
|
config,
|
|
database,
|
|
httpServer,
|
|
io,
|
|
lifecycle,
|
|
migrations,
|
|
get address() {
|
|
return address;
|
|
},
|
|
start,
|
|
stop,
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
createRuntime,
|
|
};
|