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.
@@ -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"}