@friggframework/core 2.0.0--canary.398.7664c46.0 → 2.0.0--canary.400.bed3308.0

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.
@@ -0,0 +1,230 @@
1
+ # Frigg Healthcheck Endpoint Documentation
2
+
3
+ ## Overview
4
+
5
+ The Frigg service includes comprehensive healthcheck endpoints to monitor service health, connectivity, and readiness. These endpoints follow industry best practices and are designed for use with monitoring systems, load balancers, and container orchestration platforms.
6
+
7
+ ## Endpoints
8
+
9
+ ### 1. Basic Health Check
10
+ **GET** `/health`
11
+
12
+ Simple health check endpoint that returns basic service information. No authentication required.
13
+
14
+ **Response:**
15
+ ```json
16
+ {
17
+ "status": "ok",
18
+ "timestamp": "2024-01-10T12:00:00.000Z",
19
+ "service": "frigg-core-api",
20
+ "version": "1.0.0"
21
+ }
22
+ ```
23
+
24
+ **Status Codes:**
25
+ - `200 OK` - Service is running
26
+
27
+ ### 2. Detailed Health Check
28
+ **GET** `/health/detailed`
29
+
30
+ Comprehensive health check that tests all service components and dependencies.
31
+
32
+ **Response:**
33
+ ```json
34
+ {
35
+ "service": "frigg-core-api",
36
+ "status": "healthy", // "healthy", "degraded", or "unhealthy"
37
+ "timestamp": "2024-01-10T12:00:00.000Z",
38
+ "version": "1.0.0",
39
+ "uptime": 3600, // seconds
40
+ "checks": {
41
+ "database": {
42
+ "status": "healthy",
43
+ "state": "connected",
44
+ "type": "mongodb",
45
+ "responseTime": 5 // milliseconds
46
+ },
47
+ "external_apis": {
48
+ "github": {
49
+ "status": "healthy",
50
+ "statusCode": 200,
51
+ "responseTime": 150,
52
+ "reachable": true
53
+ },
54
+ "npm": {
55
+ "status": "healthy",
56
+ "statusCode": 200,
57
+ "responseTime": 200,
58
+ "reachable": true
59
+ }
60
+ },
61
+ "integrations": {
62
+ "status": "healthy",
63
+ "modules": {
64
+ "count": 10,
65
+ "available": ["module1", "module2", "..."]
66
+ },
67
+ "integrations": {
68
+ "count": 5,
69
+ "available": ["integration1", "integration2", "..."]
70
+ }
71
+ },
72
+ "memory": {
73
+ "status": "healthy",
74
+ "rss": "150 MB",
75
+ "heapTotal": "100 MB",
76
+ "heapUsed": "80 MB",
77
+ "external": "20 MB"
78
+ }
79
+ },
80
+ "responseTime": 250 // total endpoint response time in milliseconds
81
+ }
82
+ ```
83
+
84
+ **Status Codes:**
85
+ - `200 OK` - Service is healthy or degraded (but operational)
86
+ - `503 Service Unavailable` - Service is unhealthy
87
+
88
+ ### 3. Liveness Probe
89
+ **GET** `/health/live`
90
+
91
+ Kubernetes-style liveness probe. Returns whether the service process is alive.
92
+
93
+ **Response:**
94
+ ```json
95
+ {
96
+ "status": "alive",
97
+ "timestamp": "2024-01-10T12:00:00.000Z"
98
+ }
99
+ ```
100
+
101
+ **Status Codes:**
102
+ - `200 OK` - Service process is alive
103
+
104
+ ### 4. Readiness Probe
105
+ **GET** `/health/ready`
106
+
107
+ Kubernetes-style readiness probe. Returns whether the service is ready to receive traffic.
108
+
109
+ **Response:**
110
+ ```json
111
+ {
112
+ "ready": true,
113
+ "timestamp": "2024-01-10T12:00:00.000Z",
114
+ "checks": {
115
+ "database": true,
116
+ "modules": true
117
+ }
118
+ }
119
+ ```
120
+
121
+ **Status Codes:**
122
+ - `200 OK` - Service is ready
123
+ - `503 Service Unavailable` - Service is not ready
124
+
125
+ ## Health Status Definitions
126
+
127
+ - **healthy**: All components are functioning normally
128
+ - **degraded**: Some non-critical components have issues, but core functionality is available
129
+ - **unhealthy**: Critical components are failing, service cannot function properly
130
+
131
+ ## Component Checks
132
+
133
+ ### Database Connectivity
134
+ - Checks MongoDB connection state
135
+ - Performs ping test if connected
136
+ - Reports connection state and response time
137
+
138
+ ### External API Connectivity
139
+ - Tests connectivity to external services (GitHub, npm registry)
140
+ - Configurable timeout (default: 5 seconds)
141
+ - Reports reachability and response times
142
+
143
+ ### Integration Status
144
+ - Verifies available modules and integrations are loaded
145
+ - Reports counts and lists of available components
146
+
147
+ ### Memory Usage
148
+ - Reports current memory usage statistics
149
+ - Includes RSS, heap, and external memory metrics
150
+
151
+ ## Usage Examples
152
+
153
+ ### Monitoring Systems
154
+ Configure your monitoring system to poll `/health/detailed` every 30-60 seconds:
155
+ ```bash
156
+ curl https://your-frigg-instance.com/health/detailed
157
+ ```
158
+
159
+ ### Load Balancer Health Checks
160
+ Configure load balancers to use the simple `/health` endpoint:
161
+ ```bash
162
+ curl https://your-frigg-instance.com/health
163
+ ```
164
+
165
+ ### Kubernetes Configuration
166
+ ```yaml
167
+ livenessProbe:
168
+ httpGet:
169
+ path: /health/live
170
+ port: 8080
171
+ periodSeconds: 10
172
+ timeoutSeconds: 5
173
+
174
+ readinessProbe:
175
+ httpGet:
176
+ path: /health/ready
177
+ port: 8080
178
+ initialDelaySeconds: 30
179
+ periodSeconds: 10
180
+ ```
181
+
182
+ ## Customization
183
+
184
+ ### Adding External API Checks
185
+ To add more external API checks, modify the `externalAPIs` array in the health router:
186
+ ```javascript
187
+ const externalAPIs = [
188
+ { name: 'github', url: 'https://api.github.com/status' },
189
+ { name: 'npm', url: 'https://registry.npmjs.org' },
190
+ { name: 'your-api', url: 'https://your-api.com/health' }
191
+ ];
192
+ ```
193
+
194
+ ### Adjusting Timeouts
195
+ The default timeout for external API checks is 5 seconds. Adjust as needed:
196
+ ```javascript
197
+ const checkExternalAPI = (url, timeout = 5000) => {
198
+ // ...
199
+ };
200
+ ```
201
+
202
+ ## Best Practices
203
+
204
+ 1. **No Authentication**: Basic health endpoints should not require authentication to allow monitoring systems easy access
205
+ 2. **Fast Response**: Health checks should respond quickly (< 1 second)
206
+ 3. **Graceful Degradation**: Service can continue operating even if some non-critical components fail
207
+ 4. **Detailed Logging**: Failed health checks are logged for debugging
208
+ 5. **Version Information**: Always include version information for tracking deployments
209
+
210
+ ## Troubleshooting
211
+
212
+ ### Database Connection Issues
213
+ - Check `MONGO_URI` environment variable
214
+ - Verify network connectivity to MongoDB
215
+ - Check MongoDB server status
216
+
217
+ ### External API Failures
218
+ - May indicate network issues or external service downtime
219
+ - Service continues to operate but reports "degraded" status
220
+
221
+ ### Memory Issues
222
+ - Monitor memory metrics over time
223
+ - Consider increasing container/instance memory limits if consistently high
224
+
225
+ ## Security Considerations
226
+
227
+ - Health endpoints do not expose sensitive information
228
+ - Database connection strings and credentials are never included in responses
229
+ - External API checks use read-only endpoints
230
+ - Consider IP whitelisting for detailed health endpoints in production
@@ -0,0 +1,208 @@
1
+ const { Router } = require('express');
2
+ const mongoose = require('mongoose');
3
+ const https = require('https');
4
+ const http = require('http');
5
+ const { moduleFactory, integrationFactory } = require('./../backend-utils');
6
+ const { createAppHandler } = require('./../app-handler-helpers');
7
+ const { version } = require('../../package.json');
8
+
9
+ const router = Router();
10
+
11
+ // Utility function to check external API connectivity
12
+ const checkExternalAPI = (url, timeout = 5000) => {
13
+ return new Promise((resolve) => {
14
+ const protocol = url.startsWith('https:') ? https : http;
15
+ const startTime = Date.now();
16
+
17
+ try {
18
+ const request = protocol.get(url, { timeout }, (res) => {
19
+ const responseTime = Date.now() - startTime;
20
+ resolve({
21
+ status: 'healthy',
22
+ statusCode: res.statusCode,
23
+ responseTime,
24
+ reachable: res.statusCode < 500
25
+ });
26
+ });
27
+
28
+ request.on('error', (error) => {
29
+ resolve({
30
+ status: 'unhealthy',
31
+ error: error.message,
32
+ responseTime: Date.now() - startTime,
33
+ reachable: false
34
+ });
35
+ });
36
+
37
+ request.on('timeout', () => {
38
+ request.destroy();
39
+ resolve({
40
+ status: 'timeout',
41
+ error: 'Request timeout',
42
+ responseTime: timeout,
43
+ reachable: false
44
+ });
45
+ });
46
+ } catch (error) {
47
+ resolve({
48
+ status: 'error',
49
+ error: error.message,
50
+ responseTime: Date.now() - startTime,
51
+ reachable: false
52
+ });
53
+ }
54
+ });
55
+ };
56
+
57
+ // Simple health check - no authentication required
58
+ router.get('/health', async (req, res) => {
59
+ const status = {
60
+ status: 'ok',
61
+ timestamp: new Date().toISOString(),
62
+ service: 'frigg-core-api',
63
+ version
64
+ };
65
+
66
+ res.status(200).json(status);
67
+ });
68
+
69
+ // Detailed health check with component status
70
+ router.get('/health/detailed', async (req, res) => {
71
+ const startTime = Date.now();
72
+ const checks = {
73
+ service: 'frigg-core-api',
74
+ status: 'healthy',
75
+ timestamp: new Date().toISOString(),
76
+ version,
77
+ uptime: process.uptime(),
78
+ checks: {}
79
+ };
80
+
81
+ // Check database connectivity
82
+ try {
83
+ const dbState = mongoose.connection.readyState;
84
+ const dbStateMap = {
85
+ 0: 'disconnected',
86
+ 1: 'connected',
87
+ 2: 'connecting',
88
+ 3: 'disconnecting'
89
+ };
90
+
91
+ checks.checks.database = {
92
+ status: dbState === 1 ? 'healthy' : 'unhealthy',
93
+ state: dbStateMap[dbState],
94
+ type: 'mongodb'
95
+ };
96
+
97
+ // If connected, check database responsiveness
98
+ if (dbState === 1) {
99
+ const pingStart = Date.now();
100
+ await mongoose.connection.db.admin().ping();
101
+ checks.checks.database.responseTime = Date.now() - pingStart;
102
+ }
103
+ } catch (error) {
104
+ checks.checks.database = {
105
+ status: 'unhealthy',
106
+ error: error.message,
107
+ type: 'mongodb'
108
+ };
109
+ checks.status = 'degraded';
110
+ }
111
+
112
+ // Check external API connectivity (example endpoints)
113
+ const externalAPIs = [
114
+ { name: 'github', url: 'https://api.github.com/status' },
115
+ { name: 'npm', url: 'https://registry.npmjs.org' }
116
+ ];
117
+
118
+ checks.checks.external_apis = {};
119
+
120
+ for (const api of externalAPIs) {
121
+ checks.checks.external_apis[api.name] = await checkExternalAPI(api.url);
122
+ if (!checks.checks.external_apis[api.name].reachable) {
123
+ checks.status = 'degraded';
124
+ }
125
+ }
126
+
127
+ // Check available integrations
128
+ try {
129
+ const availableModules = moduleFactory.getAll();
130
+ const availableIntegrations = integrationFactory.getAll();
131
+
132
+ checks.checks.integrations = {
133
+ status: 'healthy',
134
+ modules: {
135
+ count: Object.keys(availableModules).length,
136
+ available: Object.keys(availableModules)
137
+ },
138
+ integrations: {
139
+ count: Object.keys(availableIntegrations).length,
140
+ available: Object.keys(availableIntegrations)
141
+ }
142
+ };
143
+ } catch (error) {
144
+ checks.checks.integrations = {
145
+ status: 'unhealthy',
146
+ error: error.message
147
+ };
148
+ checks.status = 'degraded';
149
+ }
150
+
151
+ // Memory usage
152
+ const memoryUsage = process.memoryUsage();
153
+ checks.checks.memory = {
154
+ status: 'healthy',
155
+ rss: Math.round(memoryUsage.rss / 1024 / 1024) + ' MB',
156
+ heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + ' MB',
157
+ heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + ' MB',
158
+ external: Math.round(memoryUsage.external / 1024 / 1024) + ' MB'
159
+ };
160
+
161
+ // Overall response time
162
+ checks.responseTime = Date.now() - startTime;
163
+
164
+ // Set appropriate status code
165
+ const statusCode = checks.status === 'healthy' ? 200 :
166
+ checks.status === 'degraded' ? 200 : 503;
167
+
168
+ res.status(statusCode).json(checks);
169
+ });
170
+
171
+ // Liveness probe - for k8s/container orchestration
172
+ router.get('/health/live', (req, res) => {
173
+ res.status(200).json({
174
+ status: 'alive',
175
+ timestamp: new Date().toISOString()
176
+ });
177
+ });
178
+
179
+ // Readiness probe - checks if service is ready to receive traffic
180
+ router.get('/health/ready', async (req, res) => {
181
+ const checks = {
182
+ ready: true,
183
+ timestamp: new Date().toISOString(),
184
+ checks: {}
185
+ };
186
+
187
+ // Check database is connected
188
+ const dbState = mongoose.connection.readyState;
189
+ checks.checks.database = dbState === 1;
190
+
191
+ // Check critical services are loaded
192
+ try {
193
+ const modules = moduleFactory.getAll();
194
+ checks.checks.modules = Object.keys(modules).length > 0;
195
+ } catch (error) {
196
+ checks.checks.modules = false;
197
+ }
198
+
199
+ // Determine overall readiness
200
+ checks.ready = checks.checks.database && checks.checks.modules;
201
+
202
+ const statusCode = checks.ready ? 200 : 503;
203
+ res.status(statusCode).json(checks);
204
+ });
205
+
206
+ const handler = createAppHandler('HTTP Event: Health', router);
207
+
208
+ module.exports = { handler, router };
@@ -0,0 +1,136 @@
1
+ const request = require('supertest');
2
+ const express = require('express');
3
+ const { router } = require('./health');
4
+ const mongoose = require('mongoose');
5
+
6
+ // Mock mongoose connection
7
+ jest.mock('mongoose', () => ({
8
+ connection: {
9
+ readyState: 1,
10
+ db: {
11
+ admin: () => ({
12
+ ping: jest.fn().mockResolvedValue(true)
13
+ })
14
+ }
15
+ }
16
+ }));
17
+
18
+ // Mock backend-utils
19
+ jest.mock('./../backend-utils', () => ({
20
+ moduleFactory: {
21
+ getAll: () => ({
22
+ 'test-module': {},
23
+ 'another-module': {}
24
+ })
25
+ },
26
+ integrationFactory: {
27
+ getAll: () => ({
28
+ 'test-integration': {},
29
+ 'another-integration': {}
30
+ })
31
+ }
32
+ }));
33
+
34
+ describe('Health Check Endpoints', () => {
35
+ let app;
36
+
37
+ beforeEach(() => {
38
+ app = express();
39
+ app.use(router);
40
+ });
41
+
42
+ describe('GET /health', () => {
43
+ it('should return 200 with basic health status', async () => {
44
+ const response = await request(app)
45
+ .get('/health')
46
+ .expect(200);
47
+
48
+ expect(response.body).toHaveProperty('status', 'ok');
49
+ expect(response.body).toHaveProperty('timestamp');
50
+ expect(response.body).toHaveProperty('service', 'frigg-core-api');
51
+ expect(response.body).toHaveProperty('version');
52
+ });
53
+ });
54
+
55
+ describe('GET /health/detailed', () => {
56
+ it('should return 200 with detailed health status when healthy', async () => {
57
+ const response = await request(app)
58
+ .get('/health/detailed')
59
+ .expect(200);
60
+
61
+ expect(response.body).toHaveProperty('status', 'healthy');
62
+ expect(response.body).toHaveProperty('checks');
63
+ expect(response.body.checks).toHaveProperty('database');
64
+ expect(response.body.checks).toHaveProperty('external_apis');
65
+ expect(response.body.checks).toHaveProperty('integrations');
66
+ expect(response.body.checks).toHaveProperty('memory');
67
+ expect(response.body).toHaveProperty('uptime');
68
+ expect(response.body).toHaveProperty('responseTime');
69
+ });
70
+
71
+ it('should include database connectivity status', async () => {
72
+ const response = await request(app)
73
+ .get('/health/detailed')
74
+ .expect(200);
75
+
76
+ expect(response.body.checks.database).toHaveProperty('status', 'healthy');
77
+ expect(response.body.checks.database).toHaveProperty('state', 'connected');
78
+ expect(response.body.checks.database).toHaveProperty('type', 'mongodb');
79
+ });
80
+
81
+ it('should include integration information', async () => {
82
+ const response = await request(app)
83
+ .get('/health/detailed')
84
+ .expect(200);
85
+
86
+ expect(response.body.checks.integrations).toHaveProperty('status', 'healthy');
87
+ expect(response.body.checks.integrations.modules).toHaveProperty('count', 2);
88
+ expect(response.body.checks.integrations.integrations).toHaveProperty('count', 2);
89
+ });
90
+ });
91
+
92
+ describe('GET /health/live', () => {
93
+ it('should return 200 for liveness check', async () => {
94
+ const response = await request(app)
95
+ .get('/health/live')
96
+ .expect(200);
97
+
98
+ expect(response.body).toHaveProperty('status', 'alive');
99
+ expect(response.body).toHaveProperty('timestamp');
100
+ });
101
+ });
102
+
103
+ describe('GET /health/ready', () => {
104
+ it('should return 200 when service is ready', async () => {
105
+ const response = await request(app)
106
+ .get('/health/ready')
107
+ .expect(200);
108
+
109
+ expect(response.body).toHaveProperty('ready', true);
110
+ expect(response.body).toHaveProperty('checks');
111
+ expect(response.body.checks).toHaveProperty('database', true);
112
+ expect(response.body.checks).toHaveProperty('modules', true);
113
+ });
114
+
115
+ it('should return 503 when database is not connected', async () => {
116
+ // Mock disconnected database
117
+ mongoose.connection.readyState = 0;
118
+
119
+ const response = await request(app)
120
+ .get('/health/ready')
121
+ .expect(503);
122
+
123
+ expect(response.body).toHaveProperty('ready', false);
124
+ expect(response.body.checks).toHaveProperty('database', false);
125
+ });
126
+ });
127
+ });
128
+
129
+ // Test utility functions
130
+ describe('Health Check Utilities', () => {
131
+ it('should handle external API timeouts gracefully', async () => {
132
+ // This would test the checkExternalAPI function
133
+ // In a real implementation, you might want to export this function
134
+ // for easier unit testing
135
+ });
136
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.398.7664c46.0",
4
+ "version": "2.0.0--canary.400.bed3308.0",
5
5
  "dependencies": {
6
6
  "@hapi/boom": "^10.0.1",
7
7
  "aws-sdk": "^2.1200.0",
@@ -22,9 +22,9 @@
22
22
  "uuid": "^9.0.1"
23
23
  },
24
24
  "devDependencies": {
25
- "@friggframework/eslint-config": "2.0.0--canary.398.7664c46.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.398.7664c46.0",
27
- "@friggframework/test": "2.0.0--canary.398.7664c46.0",
25
+ "@friggframework/eslint-config": "2.0.0--canary.400.bed3308.0",
26
+ "@friggframework/prettier-config": "2.0.0--canary.400.bed3308.0",
27
+ "@friggframework/test": "2.0.0--canary.400.bed3308.0",
28
28
  "@types/lodash": "4.17.15",
29
29
  "@typescript-eslint/eslint-plugin": "^8.0.0",
30
30
  "chai": "^4.3.6",
@@ -53,5 +53,5 @@
53
53
  },
54
54
  "homepage": "https://github.com/friggframework/frigg#readme",
55
55
  "description": "",
56
- "gitHead": "7664c460892e0fc6f4e69b79126cd2274d44f166"
56
+ "gitHead": "bed3308a80f0e6ef89071f7266eee422e1d1fab9"
57
57
  }