-
Notifications
You must be signed in to change notification settings - Fork 140
Expand file tree
/
Copy pathserver.ts
More file actions
137 lines (113 loc) · 4.55 KB
/
Copy pathserver.ts
File metadata and controls
137 lines (113 loc) · 4.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/// <reference types="node" />
import app from './app';
import { envConfig } from './config';
import { logger } from './utils/logger.utils';
import { prisma } from './utils/prisma.utils';
import { verifyMigrationChecksums } from './utils/migration-checksum.utils';
import {
IndexerFlagsConfigError,
runIndexerFeatureFlagsStartupCheck,
} from './utils/indexer-flags-startup-check.utils';
import { checkOptionalDependencies } from './utils/startup.utils';
import { describeDatabasePoolConfig } from './utils/db-pool-config.utils';
import { stopOwnershipSnapshotCleanupJob } from './jobs/ownership-snapshot-cleanup.job';
import { connectRedis, disconnectRedis } from './utils/redis.utils';
import { broadcastServerClosing, closeAllConnections } from './utils/sse-fanout.utils';
import { buildStartupConfigSummary } from './utils/config-summary.utils';
async function startServer() {
try {
// Validate indexer feature flags before any code paths read them. We
// fail fast here so operators see every misconfiguration at once
// instead of cryptic runtime errors later in the boot sequence.
try {
runIndexerFeatureFlagsStartupCheck();
} catch (err) {
if (err instanceof IndexerFlagsConfigError) {
logger.error(
{ issues: err.issues },
'Refusing to start: indexer feature flags are misconfigured'
);
process.exit(1);
}
throw err;
}
await prisma.$connect();
logger.info('Connected to database');
await connectRedis();
logger.info('Connected to Redis');
// Surface connection-pool settings (no credentials) so connection
// exhaustion is diagnosable. Logged before the server accepts requests.
logger.info(
describeDatabasePoolConfig(),
'Database connection pool configured'
);
// Emit a structured summary of the loaded runtime config: environment
// context and key feature flags. Values flow through the masking helper,
// so no secrets or credentials are logged. See
// utils/config-summary.utils.ts for the curated field selection.
logger.info(
buildStartupConfigSummary(),
'Loaded runtime configuration summary'
);
// Verify migrations on startup
await verifyMigrationChecksums();
// Check and warn about disabled optional dependencies (non-blocking)
checkOptionalDependencies();
const server = app.listen(envConfig.PORT, () => {
logger.info(`Server running on port ${envConfig.PORT}`);
});
return server;
} catch (error) {
console.error('Failed to start server:', error);
await prisma.$disconnect();
await disconnectRedis().catch(() => {});
process.exit(1);
}
}
// Handle uncaught exceptions
process.on('uncaughtException', error => {
console.error('Uncaught Exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
function createGracefulShutdownHandler(server: ReturnType<typeof app.listen>) {
return async () => {
logger.info('Shutting down SSE connections');
broadcastServerClosing();
await new Promise(resolve => setTimeout(resolve, 1000));
closeAllConnections();
stopOwnershipSnapshotCleanupJob();
await prisma.$disconnect();
logger.info('Database connection closed');
await disconnectRedis().catch(() => {});
logger.info('Redis connection closed');
const DRAIN_WINDOW_MS = 5000;
const SHUTDOWN_TIMEOUT_MS = 30000;
app.use((_req, res, _next) => {
res.status(503).json({ error: 'Server is shutting down' });
});
const shutdownTimer = setTimeout(() => {
logger.error('Shutdown timeout reached, forcing exit');
process.exit(1);
}, SHUTDOWN_TIMEOUT_MS);
server.close(async () => {
clearTimeout(shutdownTimer);
logger.info('HTTP server closed, draining requests');
await new Promise(resolve => setTimeout(resolve, DRAIN_WINDOW_MS));
await prisma.$disconnect();
logger.info('Database connection closed');
await disconnectRedis().catch(() => {});
logger.info('Redis connection closed');
logger.info('Shutdown complete');
process.exit(0);
});
};
}
startServer().then(server => {
const shutdownHandler = createGracefulShutdownHandler(server);
process.on('SIGINT', shutdownHandler);
process.on('SIGTERM', shutdownHandler);
});