4runr-os 2.10.9 → 2.10.13
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/apps/gateway/dist/apps/gateway/src/db/docker-manager.d.ts +1 -0
- package/apps/gateway/dist/apps/gateway/src/db/docker-manager.d.ts.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/docker-manager.js +22 -2
- package/apps/gateway/dist/apps/gateway/src/db/docker-manager.js.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/init.d.ts.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/init.js +8 -0
- package/apps/gateway/dist/apps/gateway/src/db/init.js.map +1 -1
- package/apps/gateway/package-lock.json +13 -13
- package/apps/gateway/src/db/docker-manager.ts +26 -3
- package/apps/gateway/src/db/init.ts +299 -289
- package/apps/gateway/src/health/index.ts +268 -268
- package/dist/boot-sequence.d.ts +24 -0
- package/dist/boot-sequence.d.ts.map +1 -0
- package/dist/boot-sequence.js +188 -0
- package/dist/boot-sequence.js.map +1 -0
- package/dist/index.js +52 -6
- package/dist/index.js.map +1 -1
- package/dist/tui-handlers.js +3 -3
- package/dist/tui-handlers.js.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +71 -25
- package/dist/version-check.js.map +1 -1
- package/dist/watchdog.d.ts +13 -0
- package/dist/watchdog.d.ts.map +1 -0
- package/dist/watchdog.js +192 -0
- package/dist/watchdog.js.map +1 -0
- package/mk3-tui/src/ui/portal_monitoring.rs +259 -259
- package/package.json +2 -2
- package/prisma/schema.prisma +470 -470
- package/scripts/postinstall-gateway.js +63 -63
|
@@ -1,268 +1,268 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Health check utilities
|
|
3
|
-
* Provides comprehensive health checking for dependencies and system status
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { getRedisClient, isRedisAvailable } from '../db/redis.js';
|
|
7
|
-
import { getRunStore } from '../runs/index.js';
|
|
8
|
-
import { getQueue } from '../queue/index.js';
|
|
9
|
-
import { env, createLogger } from '@4runr/shared';
|
|
10
|
-
|
|
11
|
-
const logger = createLogger('Gateway:Health');
|
|
12
|
-
|
|
13
|
-
export interface HealthCheckResult {
|
|
14
|
-
status: 'healthy' | 'unhealthy' | 'degraded';
|
|
15
|
-
checks: {
|
|
16
|
-
database?: DependencyCheck;
|
|
17
|
-
redis?: DependencyCheck;
|
|
18
|
-
queue?: DependencyCheck;
|
|
19
|
-
};
|
|
20
|
-
timestamp: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface DependencyCheck {
|
|
24
|
-
status: 'up' | 'down' | 'degraded';
|
|
25
|
-
responseTime?: number;
|
|
26
|
-
error?: string;
|
|
27
|
-
message?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Check database connectivity
|
|
32
|
-
*/
|
|
33
|
-
export async function checkDatabase(): Promise<DependencyCheck> {
|
|
34
|
-
const startTime = Date.now();
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const store = getRunStore();
|
|
38
|
-
|
|
39
|
-
// Try a simple query
|
|
40
|
-
if (env.DATABASE_URL) {
|
|
41
|
-
// If using Prisma, check connection
|
|
42
|
-
const prisma = (store as any).prisma as any;
|
|
43
|
-
if (prisma) {
|
|
44
|
-
await prisma.$queryRaw`SELECT 1`;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const responseTime = Date.now() - startTime;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
status: 'up',
|
|
52
|
-
responseTime,
|
|
53
|
-
message: 'Database connection healthy',
|
|
54
|
-
};
|
|
55
|
-
} catch (error) {
|
|
56
|
-
const responseTime = Date.now() - startTime;
|
|
57
|
-
logger.warn('Database health check failed', {
|
|
58
|
-
error: error instanceof Error ? error.message : String(error),
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
status: 'down',
|
|
63
|
-
responseTime,
|
|
64
|
-
error: error instanceof Error ? error.message : String(error),
|
|
65
|
-
message: 'Database connection failed',
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Check Redis connectivity
|
|
72
|
-
*/
|
|
73
|
-
export async function checkRedis(): Promise<DependencyCheck> {
|
|
74
|
-
const startTime = Date.now();
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
const hasRedisUrl = Boolean(env.REDIS_URL || process.env['REDIS_URL']);
|
|
78
|
-
if (!hasRedisUrl) {
|
|
79
|
-
return {
|
|
80
|
-
status: 'down',
|
|
81
|
-
message: 'REDIS_URL not set — 4Runr TUI spawns local Redis via Docker by default; set REDIS_URL or run Redis yourself',
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const available = await isRedisAvailable();
|
|
86
|
-
if (!available) {
|
|
87
|
-
return {
|
|
88
|
-
status: 'down',
|
|
89
|
-
message: 'REDIS_URL set but Redis is unreachable (start redis-server or Docker, then retry)',
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const redis = await getRedisClient();
|
|
94
|
-
if (!redis) {
|
|
95
|
-
return {
|
|
96
|
-
status: 'down',
|
|
97
|
-
message: 'Redis client not available',
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Simple ping test
|
|
102
|
-
const result = await redis.ping();
|
|
103
|
-
const responseTime = Date.now() - startTime;
|
|
104
|
-
|
|
105
|
-
if (result === 'PONG') {
|
|
106
|
-
return {
|
|
107
|
-
status: 'up',
|
|
108
|
-
responseTime,
|
|
109
|
-
message: 'Redis connection healthy',
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
status: 'degraded',
|
|
115
|
-
responseTime,
|
|
116
|
-
message: 'Redis ping returned unexpected result',
|
|
117
|
-
};
|
|
118
|
-
} catch (error) {
|
|
119
|
-
const responseTime = Date.now() - startTime;
|
|
120
|
-
logger.warn('Redis health check failed', {
|
|
121
|
-
error: error instanceof Error ? error.message : String(error),
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
status: 'down',
|
|
126
|
-
responseTime,
|
|
127
|
-
error: error instanceof Error ? error.message : String(error),
|
|
128
|
-
message: 'Redis connection failed',
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Check queue system status
|
|
135
|
-
*/
|
|
136
|
-
export async function checkQueue(): Promise<DependencyCheck> {
|
|
137
|
-
const startTime = Date.now();
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const queue = getQueue();
|
|
141
|
-
if (!queue) {
|
|
142
|
-
return {
|
|
143
|
-
status: 'down',
|
|
144
|
-
message: 'Queue system not initialized',
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Check if queue is accessible - try to get queue info
|
|
149
|
-
// BullMQ queues don't expose client status directly, so we check if queue exists
|
|
150
|
-
try {
|
|
151
|
-
const queueName = (queue as any).name || 'unknown';
|
|
152
|
-
const responseTime = Date.now() - startTime;
|
|
153
|
-
|
|
154
|
-
// If queue exists and was initialized, consider it healthy
|
|
155
|
-
// Actual connection status is checked via Redis health check
|
|
156
|
-
return {
|
|
157
|
-
status: 'up',
|
|
158
|
-
responseTime,
|
|
159
|
-
message: `Queue system healthy (${queueName})`,
|
|
160
|
-
};
|
|
161
|
-
} catch (err) {
|
|
162
|
-
const responseTime = Date.now() - startTime;
|
|
163
|
-
return {
|
|
164
|
-
status: 'degraded',
|
|
165
|
-
responseTime,
|
|
166
|
-
message: 'Queue system accessible but status unknown',
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
} catch (error) {
|
|
170
|
-
const responseTime = Date.now() - startTime;
|
|
171
|
-
logger.warn('Queue health check failed', {
|
|
172
|
-
error: error instanceof Error ? error.message : String(error),
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
status: 'down',
|
|
177
|
-
responseTime,
|
|
178
|
-
error: error instanceof Error ? error.message : String(error),
|
|
179
|
-
message: 'Queue system check failed',
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Perform all health checks
|
|
186
|
-
*/
|
|
187
|
-
export async function performHealthChecks(): Promise<HealthCheckResult> {
|
|
188
|
-
const checks: HealthCheckResult['checks'] = {};
|
|
189
|
-
|
|
190
|
-
// Run checks in parallel
|
|
191
|
-
const [databaseCheck, redisCheck, queueCheck] = await Promise.allSettled([
|
|
192
|
-
checkDatabase(),
|
|
193
|
-
checkRedis(),
|
|
194
|
-
checkQueue(),
|
|
195
|
-
]);
|
|
196
|
-
|
|
197
|
-
if (databaseCheck.status === 'fulfilled') {
|
|
198
|
-
checks.database = databaseCheck.value;
|
|
199
|
-
} else {
|
|
200
|
-
checks.database = {
|
|
201
|
-
status: 'down',
|
|
202
|
-
error: databaseCheck.reason?.message || 'Database check failed',
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (redisCheck.status === 'fulfilled') {
|
|
207
|
-
checks.redis = redisCheck.value;
|
|
208
|
-
} else {
|
|
209
|
-
checks.redis = {
|
|
210
|
-
status: 'down',
|
|
211
|
-
error: redisCheck.reason?.message || 'Redis check failed',
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (queueCheck.status === 'fulfilled') {
|
|
216
|
-
checks.queue = queueCheck.value;
|
|
217
|
-
} else {
|
|
218
|
-
checks.queue = {
|
|
219
|
-
status: 'down',
|
|
220
|
-
error: queueCheck.reason?.message || 'Queue check failed',
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Overall status: **database** is critical for readiness; Redis/queue are optional (local dev often has no Redis).
|
|
225
|
-
const db = checks.database;
|
|
226
|
-
const databaseDown = db?.status === 'down';
|
|
227
|
-
const hasDegraded = Object.values(checks).some(
|
|
228
|
-
(check) => check.status === 'degraded'
|
|
229
|
-
);
|
|
230
|
-
const optionalDown =
|
|
231
|
-
checks.redis?.status === 'down' || checks.queue?.status === 'down';
|
|
232
|
-
|
|
233
|
-
let status: HealthCheckResult['status'] = 'healthy';
|
|
234
|
-
if (databaseDown) {
|
|
235
|
-
status = 'unhealthy';
|
|
236
|
-
} else if (hasDegraded || optionalDown) {
|
|
237
|
-
status = 'degraded';
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
status,
|
|
242
|
-
checks,
|
|
243
|
-
timestamp: new Date().toISOString(),
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Check if service is ready to accept traffic.
|
|
249
|
-
* Critical: Postgres when persistence uses the DB. Redis and queue are optional for readiness.
|
|
250
|
-
*/
|
|
251
|
-
export async function isReady(): Promise<boolean> {
|
|
252
|
-
const health = await performHealthChecks();
|
|
253
|
-
|
|
254
|
-
const db = health.checks.database;
|
|
255
|
-
const databaseOk = !db || db.status === 'up';
|
|
256
|
-
|
|
257
|
-
return databaseOk;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Check if service is alive (simple liveness check)
|
|
262
|
-
*/
|
|
263
|
-
export function isAlive(): boolean {
|
|
264
|
-
// Liveness is simple - just check if process is running
|
|
265
|
-
// More complex checks could verify memory, CPU, etc.
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Health check utilities
|
|
3
|
+
* Provides comprehensive health checking for dependencies and system status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getRedisClient, isRedisAvailable } from '../db/redis.js';
|
|
7
|
+
import { getRunStore } from '../runs/index.js';
|
|
8
|
+
import { getQueue } from '../queue/index.js';
|
|
9
|
+
import { env, createLogger } from '@4runr/shared';
|
|
10
|
+
|
|
11
|
+
const logger = createLogger('Gateway:Health');
|
|
12
|
+
|
|
13
|
+
export interface HealthCheckResult {
|
|
14
|
+
status: 'healthy' | 'unhealthy' | 'degraded';
|
|
15
|
+
checks: {
|
|
16
|
+
database?: DependencyCheck;
|
|
17
|
+
redis?: DependencyCheck;
|
|
18
|
+
queue?: DependencyCheck;
|
|
19
|
+
};
|
|
20
|
+
timestamp: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DependencyCheck {
|
|
24
|
+
status: 'up' | 'down' | 'degraded';
|
|
25
|
+
responseTime?: number;
|
|
26
|
+
error?: string;
|
|
27
|
+
message?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check database connectivity
|
|
32
|
+
*/
|
|
33
|
+
export async function checkDatabase(): Promise<DependencyCheck> {
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const store = getRunStore();
|
|
38
|
+
|
|
39
|
+
// Try a simple query
|
|
40
|
+
if (env.DATABASE_URL) {
|
|
41
|
+
// If using Prisma, check connection
|
|
42
|
+
const prisma = (store as any).prisma as any;
|
|
43
|
+
if (prisma) {
|
|
44
|
+
await prisma.$queryRaw`SELECT 1`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const responseTime = Date.now() - startTime;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
status: 'up',
|
|
52
|
+
responseTime,
|
|
53
|
+
message: 'Database connection healthy',
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
const responseTime = Date.now() - startTime;
|
|
57
|
+
logger.warn('Database health check failed', {
|
|
58
|
+
error: error instanceof Error ? error.message : String(error),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
status: 'down',
|
|
63
|
+
responseTime,
|
|
64
|
+
error: error instanceof Error ? error.message : String(error),
|
|
65
|
+
message: 'Database connection failed',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check Redis connectivity
|
|
72
|
+
*/
|
|
73
|
+
export async function checkRedis(): Promise<DependencyCheck> {
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const hasRedisUrl = Boolean(env.REDIS_URL || process.env['REDIS_URL']);
|
|
78
|
+
if (!hasRedisUrl) {
|
|
79
|
+
return {
|
|
80
|
+
status: 'down',
|
|
81
|
+
message: 'REDIS_URL not set — 4Runr TUI spawns local Redis via Docker by default; set REDIS_URL or run Redis yourself',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const available = await isRedisAvailable();
|
|
86
|
+
if (!available) {
|
|
87
|
+
return {
|
|
88
|
+
status: 'down',
|
|
89
|
+
message: 'REDIS_URL set but Redis is unreachable (start redis-server or Docker, then retry)',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const redis = await getRedisClient();
|
|
94
|
+
if (!redis) {
|
|
95
|
+
return {
|
|
96
|
+
status: 'down',
|
|
97
|
+
message: 'Redis client not available',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Simple ping test
|
|
102
|
+
const result = await redis.ping();
|
|
103
|
+
const responseTime = Date.now() - startTime;
|
|
104
|
+
|
|
105
|
+
if (result === 'PONG') {
|
|
106
|
+
return {
|
|
107
|
+
status: 'up',
|
|
108
|
+
responseTime,
|
|
109
|
+
message: 'Redis connection healthy',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
status: 'degraded',
|
|
115
|
+
responseTime,
|
|
116
|
+
message: 'Redis ping returned unexpected result',
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const responseTime = Date.now() - startTime;
|
|
120
|
+
logger.warn('Redis health check failed', {
|
|
121
|
+
error: error instanceof Error ? error.message : String(error),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
status: 'down',
|
|
126
|
+
responseTime,
|
|
127
|
+
error: error instanceof Error ? error.message : String(error),
|
|
128
|
+
message: 'Redis connection failed',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check queue system status
|
|
135
|
+
*/
|
|
136
|
+
export async function checkQueue(): Promise<DependencyCheck> {
|
|
137
|
+
const startTime = Date.now();
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const queue = getQueue();
|
|
141
|
+
if (!queue) {
|
|
142
|
+
return {
|
|
143
|
+
status: 'down',
|
|
144
|
+
message: 'Queue system not initialized',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check if queue is accessible - try to get queue info
|
|
149
|
+
// BullMQ queues don't expose client status directly, so we check if queue exists
|
|
150
|
+
try {
|
|
151
|
+
const queueName = (queue as any).name || 'unknown';
|
|
152
|
+
const responseTime = Date.now() - startTime;
|
|
153
|
+
|
|
154
|
+
// If queue exists and was initialized, consider it healthy
|
|
155
|
+
// Actual connection status is checked via Redis health check
|
|
156
|
+
return {
|
|
157
|
+
status: 'up',
|
|
158
|
+
responseTime,
|
|
159
|
+
message: `Queue system healthy (${queueName})`,
|
|
160
|
+
};
|
|
161
|
+
} catch (err) {
|
|
162
|
+
const responseTime = Date.now() - startTime;
|
|
163
|
+
return {
|
|
164
|
+
status: 'degraded',
|
|
165
|
+
responseTime,
|
|
166
|
+
message: 'Queue system accessible but status unknown',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const responseTime = Date.now() - startTime;
|
|
171
|
+
logger.warn('Queue health check failed', {
|
|
172
|
+
error: error instanceof Error ? error.message : String(error),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
status: 'down',
|
|
177
|
+
responseTime,
|
|
178
|
+
error: error instanceof Error ? error.message : String(error),
|
|
179
|
+
message: 'Queue system check failed',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Perform all health checks
|
|
186
|
+
*/
|
|
187
|
+
export async function performHealthChecks(): Promise<HealthCheckResult> {
|
|
188
|
+
const checks: HealthCheckResult['checks'] = {};
|
|
189
|
+
|
|
190
|
+
// Run checks in parallel
|
|
191
|
+
const [databaseCheck, redisCheck, queueCheck] = await Promise.allSettled([
|
|
192
|
+
checkDatabase(),
|
|
193
|
+
checkRedis(),
|
|
194
|
+
checkQueue(),
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
if (databaseCheck.status === 'fulfilled') {
|
|
198
|
+
checks.database = databaseCheck.value;
|
|
199
|
+
} else {
|
|
200
|
+
checks.database = {
|
|
201
|
+
status: 'down',
|
|
202
|
+
error: databaseCheck.reason?.message || 'Database check failed',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (redisCheck.status === 'fulfilled') {
|
|
207
|
+
checks.redis = redisCheck.value;
|
|
208
|
+
} else {
|
|
209
|
+
checks.redis = {
|
|
210
|
+
status: 'down',
|
|
211
|
+
error: redisCheck.reason?.message || 'Redis check failed',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (queueCheck.status === 'fulfilled') {
|
|
216
|
+
checks.queue = queueCheck.value;
|
|
217
|
+
} else {
|
|
218
|
+
checks.queue = {
|
|
219
|
+
status: 'down',
|
|
220
|
+
error: queueCheck.reason?.message || 'Queue check failed',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Overall status: **database** is critical for readiness; Redis/queue are optional (local dev often has no Redis).
|
|
225
|
+
const db = checks.database;
|
|
226
|
+
const databaseDown = db?.status === 'down';
|
|
227
|
+
const hasDegraded = Object.values(checks).some(
|
|
228
|
+
(check) => check.status === 'degraded'
|
|
229
|
+
);
|
|
230
|
+
const optionalDown =
|
|
231
|
+
checks.redis?.status === 'down' || checks.queue?.status === 'down';
|
|
232
|
+
|
|
233
|
+
let status: HealthCheckResult['status'] = 'healthy';
|
|
234
|
+
if (databaseDown) {
|
|
235
|
+
status = 'unhealthy';
|
|
236
|
+
} else if (hasDegraded || optionalDown) {
|
|
237
|
+
status = 'degraded';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
status,
|
|
242
|
+
checks,
|
|
243
|
+
timestamp: new Date().toISOString(),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if service is ready to accept traffic.
|
|
249
|
+
* Critical: Postgres when persistence uses the DB. Redis and queue are optional for readiness.
|
|
250
|
+
*/
|
|
251
|
+
export async function isReady(): Promise<boolean> {
|
|
252
|
+
const health = await performHealthChecks();
|
|
253
|
+
|
|
254
|
+
const db = health.checks.database;
|
|
255
|
+
const databaseOk = !db || db.status === 'up';
|
|
256
|
+
|
|
257
|
+
return databaseOk;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if service is alive (simple liveness check)
|
|
262
|
+
*/
|
|
263
|
+
export function isAlive(): boolean {
|
|
264
|
+
// Liveness is simple - just check if process is running
|
|
265
|
+
// More complex checks could verify memory, CPU, etc.
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot sequence - ensures clean system state before launching 4r
|
|
3
|
+
* Handles stale processes, in-progress updates, and broken installs
|
|
4
|
+
*/
|
|
5
|
+
interface BootCheckResult {
|
|
6
|
+
ok: boolean;
|
|
7
|
+
needsUpdate?: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
warnings?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Kill any stale 4r/Gateway processes that might lock files
|
|
13
|
+
*/
|
|
14
|
+
export declare function killStaleProcesses(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Wait for any in-progress npm install -g 4runr-os to complete
|
|
17
|
+
*/
|
|
18
|
+
export declare function waitForInProgressUpdate(maxWaitMs?: number): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Perform full boot sequence health check
|
|
21
|
+
*/
|
|
22
|
+
export declare function performBootSequence(): Promise<BootCheckResult>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=boot-sequence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot-sequence.d.ts","sourceRoot":"","sources":["../src/boot-sequence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,UAAU,eAAe;IACvB,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CA4CzC;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,GAAE,MAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CA6B1F;AAuED;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,eAAe,CAAC,CA+CpE"}
|