@adalo/metrics 0.0.0-staging.1

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.
Files changed (85) hide show
  1. package/.env.example +14 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc +61 -0
  4. package/.github/pull_request_template.md +14 -0
  5. package/.github/workflows/code-style.yml +29 -0
  6. package/.github/workflows/deploy-staging.yml +34 -0
  7. package/.github/workflows/deploy.yml +29 -0
  8. package/.github/workflows/tests.yml +17 -0
  9. package/.idea/codeStyles/Project.xml +101 -0
  10. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  11. package/.idea/git_toolbox_prj.xml +15 -0
  12. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  13. package/.idea/jsLibraryMappings.xml +6 -0
  14. package/.idea/prettier.xml +6 -0
  15. package/.idea/vcs.xml +6 -0
  16. package/.prettierrc +10 -0
  17. package/README-health.md +234 -0
  18. package/README.md +120 -0
  19. package/__tests__/metricsRedisClient.test.js +138 -0
  20. package/babel.config.js +20 -0
  21. package/lib/health/databaseChecker.d.ts +43 -0
  22. package/lib/health/databaseChecker.d.ts.map +1 -0
  23. package/lib/health/databaseChecker.js +189 -0
  24. package/lib/health/databaseChecker.js.map +1 -0
  25. package/lib/health/healthCheckCache.d.ts +59 -0
  26. package/lib/health/healthCheckCache.d.ts.map +1 -0
  27. package/lib/health/healthCheckCache.js +187 -0
  28. package/lib/health/healthCheckCache.js.map +1 -0
  29. package/lib/health/healthCheckClient.d.ts +124 -0
  30. package/lib/health/healthCheckClient.d.ts.map +1 -0
  31. package/lib/health/healthCheckClient.js +324 -0
  32. package/lib/health/healthCheckClient.js.map +1 -0
  33. package/lib/health/healthCheckUtils.d.ts +52 -0
  34. package/lib/health/healthCheckUtils.d.ts.map +1 -0
  35. package/lib/health/healthCheckUtils.js +129 -0
  36. package/lib/health/healthCheckUtils.js.map +1 -0
  37. package/lib/health/healthCheckWorker.d.ts +2 -0
  38. package/lib/health/healthCheckWorker.d.ts.map +1 -0
  39. package/lib/health/healthCheckWorker.js +70 -0
  40. package/lib/health/healthCheckWorker.js.map +1 -0
  41. package/lib/index.d.ts +10 -0
  42. package/lib/index.d.ts.map +1 -0
  43. package/lib/index.js +105 -0
  44. package/lib/index.js.map +1 -0
  45. package/lib/metrics/baseMetricsClient.d.ts +174 -0
  46. package/lib/metrics/baseMetricsClient.d.ts.map +1 -0
  47. package/lib/metrics/baseMetricsClient.js +428 -0
  48. package/lib/metrics/baseMetricsClient.js.map +1 -0
  49. package/lib/metrics/metricsClient.d.ts +95 -0
  50. package/lib/metrics/metricsClient.d.ts.map +1 -0
  51. package/lib/metrics/metricsClient.js +239 -0
  52. package/lib/metrics/metricsClient.js.map +1 -0
  53. package/lib/metrics/metricsDatabaseClient.d.ts +74 -0
  54. package/lib/metrics/metricsDatabaseClient.d.ts.map +1 -0
  55. package/lib/metrics/metricsDatabaseClient.js +218 -0
  56. package/lib/metrics/metricsDatabaseClient.js.map +1 -0
  57. package/lib/metrics/metricsQueueRedisClient.d.ts +57 -0
  58. package/lib/metrics/metricsQueueRedisClient.d.ts.map +1 -0
  59. package/lib/metrics/metricsQueueRedisClient.js +277 -0
  60. package/lib/metrics/metricsQueueRedisClient.js.map +1 -0
  61. package/lib/metrics/metricsRedisClient.d.ts +71 -0
  62. package/lib/metrics/metricsRedisClient.d.ts.map +1 -0
  63. package/lib/metrics/metricsRedisClient.js +370 -0
  64. package/lib/metrics/metricsRedisClient.js.map +1 -0
  65. package/lib/redisUtils.d.ts +53 -0
  66. package/lib/redisUtils.d.ts.map +1 -0
  67. package/lib/redisUtils.js +140 -0
  68. package/lib/redisUtils.js.map +1 -0
  69. package/package.json +66 -0
  70. package/scripts/README.md +43 -0
  71. package/scripts/clearMetrics.js +6 -0
  72. package/src/health/databaseChecker.js +183 -0
  73. package/src/health/healthCheckCache.js +216 -0
  74. package/src/health/healthCheckClient.js +347 -0
  75. package/src/health/healthCheckUtils.js +125 -0
  76. package/src/health/healthCheckWorker.js +71 -0
  77. package/src/index.ts +9 -0
  78. package/src/metrics/baseMetricsClient.js +494 -0
  79. package/src/metrics/metricsClient.js +284 -0
  80. package/src/metrics/metricsDatabaseClient.js +236 -0
  81. package/src/metrics/metricsQueueRedisClient.js +352 -0
  82. package/src/metrics/metricsRedisClient.js +417 -0
  83. package/src/redisUtils.js +155 -0
  84. package/tsconfig.json +19 -0
  85. package/tsconfig.types.json +11 -0
package/.env.example ADDED
@@ -0,0 +1,14 @@
1
+ BUILD_APP_NAME="staging"
2
+ HOSTNAME="staging-queue-metricus"
3
+ BUILD_DYNO_PROCESS_TYPE="web"
4
+ METRICS_ENABLED=true
5
+ # Set to true to collect app_requests_total and app_requests_total_duration (default: false)
6
+ # METRICS_HTTP_ENABLED=false
7
+ METRICS_LOG_VALUES=true
8
+ METRICS_PUSHGATEWAY_URL=https://vm-agent.infradalogs.adalo.com
9
+ METRICS_PUSHGATEWAY_SECRET="METRICS_PUSHGATEWAY_SECRET"
10
+ METRICS_INTERVAL_SEC=60
11
+ # METRICS_REMOVE_OLD_METRICS=false (set true to delete this instance's metrics from VM on exit)
12
+
13
+
14
+
package/.eslintignore ADDED
@@ -0,0 +1,3 @@
1
+ lib/
2
+ node_modules/
3
+ babel.config.js
package/.eslintrc ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "extends": [
3
+ "airbnb-base",
4
+ "airbnb-typescript/base",
5
+ "prettier" // Prettier must always be last as it overrides all other configs (https://github.com/prettier/eslint-config-prettier)
6
+ ],
7
+ "parser": "@typescript-eslint/parser",
8
+ "parserOptions": {
9
+ "project": "./tsconfig.json"
10
+ },
11
+ "plugins": ["@typescript-eslint"],
12
+ "env": {
13
+ "browser": true,
14
+ "commonjs": true,
15
+ "es6": true,
16
+ "node": true
17
+ },
18
+ "settings": {
19
+ "import/resolver": {
20
+ "typescript": {} // this loads <rootdir>/tsconfig.json
21
+ }
22
+ },
23
+ "overrides": [
24
+ {
25
+ "files": ["**/*.test.js"],
26
+ "env": {
27
+ "jest": true
28
+ }
29
+ }
30
+ ],
31
+ "rules": {
32
+ "curly": ["error", "multi-line"],
33
+ "lines-between-class-members": [
34
+ "error",
35
+ "always",
36
+ {
37
+ "exceptAfterSingleLine": true
38
+ }
39
+ ],
40
+ "@typescript-eslint/no-unused-vars": [
41
+ "error",
42
+ {
43
+ "vars": "all",
44
+ "args": "none",
45
+ "ignoreRestSiblings": true
46
+ }
47
+ ],
48
+
49
+ // Disabled rules
50
+ "arrow-body-style": "off",
51
+ "import/prefer-default-export": "off",
52
+ "no-param-reassign": "off",
53
+ "no-restricted-syntax": "off",
54
+ "@typescript-eslint/no-shadow": "off",
55
+ "no-underscore-dangle": "off",
56
+ "class-methods-use-this": "off",
57
+ "no-await-in-loop": "off",
58
+ "no-console": "off",
59
+ "no-continue": "off"
60
+ }
61
+ }
@@ -0,0 +1,14 @@
1
+ ## Problem
2
+ *Describe the specific bug or product issue that's being fixed.*
3
+
4
+ ## Solution
5
+ *What did you do to fix it? Remember gif's or video of frontend fixes.*
6
+
7
+ ## Additional Notes (optional)
8
+
9
+ - Link to Tech Spec in Notion
10
+
11
+
12
+ ## Related PR's (optional)
13
+
14
+ ## New Packages, Environment Variables (optional)
@@ -0,0 +1,29 @@
1
+ name: Check Prettier and ESLint conformance
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ prettier:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Check out repo
15
+ uses: actions/checkout@v3
16
+
17
+ - name: Setup Node.js
18
+ uses: actions/setup-node@v3
19
+ with:
20
+ node-version: '18.x'
21
+
22
+ - name: Installing dependencies
23
+ run: yarn install --frozen-lockfile
24
+
25
+ - name: Run Prettier checks
26
+ run: yarn prettier:check
27
+
28
+ - name: Run ESLint checks
29
+ run: yarn lint:check
@@ -0,0 +1,34 @@
1
+ # Publish a staging build to npm with tag "staging".
2
+ # Push (or merge) to branch "staging" to publish. Consuming apps can then use
3
+ # "@adalo/metrics": "staging"
4
+ # to test changes on staging before merging to main.
5
+ name: Deploy Staging
6
+
7
+ on:
8
+ push:
9
+ branches:
10
+ - staging
11
+
12
+ jobs:
13
+ build-and-publish:
14
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v3
18
+ with:
19
+ fetch-depth: 0
20
+ - name: Setup Node.js
21
+ uses: actions/setup-node@v3
22
+ with:
23
+ node-version: '18.x'
24
+ registry-url: 'https://registry.npmjs.org'
25
+ - name: Installing dependencies
26
+ run: yarn install --frozen-lockfile
27
+ - name: Build lib
28
+ run: yarn run build
29
+ - name: Set staging version
30
+ run: npm version 0.0.0-staging.${{ github.run_number }} --no-git-tag-version --allow-same-version
31
+ - name: Publish to NPM (tag staging)
32
+ run: npm publish --tag staging
33
+ env:
34
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
@@ -0,0 +1,29 @@
1
+ name: Deploy Production
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ build:
10
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v3
14
+ with:
15
+ fetch-depth: 0 # Fetch all commits across all branches. Default behaviour is efficient but breaks merge-release.
16
+ - name: Setup Node.js
17
+ uses: actions/setup-node@v3
18
+ with:
19
+ node-version: '18.x'
20
+ registry-url: 'https://registry.npmjs.org'
21
+ - name: Installing dependencies
22
+ run: yarn install --frozen-lockfile
23
+ - name: Build lib
24
+ run: yarn run build
25
+ - name: Publish to NPM
26
+ uses: AdaloHQ/npm-publish@v1
27
+ env:
28
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
29
+ NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
@@ -0,0 +1,17 @@
1
+ name: CI
2
+
3
+ on: pull_request
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v3
10
+ - name: Setup Node.js
11
+ uses: actions/setup-node@v3
12
+ with:
13
+ node-version: '18.x'
14
+ - name: Installing dependencies
15
+ run: yarn install --frozen-lockfile
16
+ - name: Running tests
17
+ run: yarn test
@@ -0,0 +1,101 @@
1
+ <component name="ProjectCodeStyleConfiguration">
2
+ <code_scheme name="Project" version="173">
3
+ <option name="OTHER_INDENT_OPTIONS">
4
+ <value>
5
+ <option name="INDENT_SIZE" value="2" />
6
+ <option name="TAB_SIZE" value="2" />
7
+ </value>
8
+ </option>
9
+ <HTMLCodeStyleSettings>
10
+ <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
11
+ </HTMLCodeStyleSettings>
12
+ <JSCodeStyleSettings version="0">
13
+ <option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
14
+ <option name="FORCE_SEMICOLON_STYLE" value="true" />
15
+ <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACKETS" value="true" />
16
+ <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
17
+ <option name="USE_DOUBLE_QUOTES" value="false" />
18
+ <option name="FORCE_QUOTE_STYlE" value="true" />
19
+ <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
20
+ <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
21
+ <option name="SPACES_WITHIN_IMPORTS" value="true" />
22
+ <option name="SPACES_WITHIN_INTERPOLATION_EXPRESSIONS" value="true" />
23
+ </JSCodeStyleSettings>
24
+ <TypeScriptCodeStyleSettings version="0">
25
+ <option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
26
+ <option name="FORCE_SEMICOLON_STYLE" value="true" />
27
+ <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
28
+ <option name="USE_DOUBLE_QUOTES" value="false" />
29
+ <option name="FORCE_QUOTE_STYlE" value="true" />
30
+ <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
31
+ <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
32
+ <option name="SPACES_WITHIN_IMPORTS" value="true" />
33
+ </TypeScriptCodeStyleSettings>
34
+ <VueCodeStyleSettings>
35
+ <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
36
+ <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
37
+ </VueCodeStyleSettings>
38
+ <codeStyleSettings language="CSS">
39
+ <indentOptions>
40
+ <option name="INDENT_SIZE" value="2" />
41
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
42
+ <option name="TAB_SIZE" value="2" />
43
+ </indentOptions>
44
+ </codeStyleSettings>
45
+ <codeStyleSettings language="Gherkin">
46
+ <indentOptions>
47
+ <option name="TAB_SIZE" value="2" />
48
+ </indentOptions>
49
+ </codeStyleSettings>
50
+ <codeStyleSettings language="HTML">
51
+ <option name="SOFT_MARGINS" value="80" />
52
+ <indentOptions>
53
+ <option name="INDENT_SIZE" value="2" />
54
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
55
+ <option name="TAB_SIZE" value="2" />
56
+ </indentOptions>
57
+ </codeStyleSettings>
58
+ <codeStyleSettings language="JSON">
59
+ <indentOptions>
60
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
61
+ </indentOptions>
62
+ </codeStyleSettings>
63
+ <codeStyleSettings language="JavaScript">
64
+ <option name="BLOCK_COMMENT_ADD_SPACE" value="true" />
65
+ <option name="IF_BRACE_FORCE" value="1" />
66
+ <option name="DOWHILE_BRACE_FORCE" value="1" />
67
+ <option name="WHILE_BRACE_FORCE" value="1" />
68
+ <option name="FOR_BRACE_FORCE" value="1" />
69
+ <option name="SOFT_MARGINS" value="80" />
70
+ <indentOptions>
71
+ <option name="INDENT_SIZE" value="2" />
72
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
73
+ <option name="TAB_SIZE" value="2" />
74
+ </indentOptions>
75
+ </codeStyleSettings>
76
+ <codeStyleSettings language="LESS">
77
+ <indentOptions>
78
+ <option name="TAB_SIZE" value="2" />
79
+ </indentOptions>
80
+ </codeStyleSettings>
81
+ <codeStyleSettings language="TypeScript">
82
+ <option name="BLOCK_COMMENT_ADD_SPACE" value="true" />
83
+ <option name="IF_BRACE_FORCE" value="1" />
84
+ <option name="DOWHILE_BRACE_FORCE" value="1" />
85
+ <option name="WHILE_BRACE_FORCE" value="1" />
86
+ <option name="FOR_BRACE_FORCE" value="1" />
87
+ <option name="SOFT_MARGINS" value="80" />
88
+ <indentOptions>
89
+ <option name="INDENT_SIZE" value="2" />
90
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
91
+ <option name="TAB_SIZE" value="2" />
92
+ </indentOptions>
93
+ </codeStyleSettings>
94
+ <codeStyleSettings language="Vue">
95
+ <option name="SOFT_MARGINS" value="80" />
96
+ <indentOptions>
97
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
98
+ </indentOptions>
99
+ </codeStyleSettings>
100
+ </code_scheme>
101
+ </component>
@@ -0,0 +1,5 @@
1
+ <component name="ProjectCodeStyleConfiguration">
2
+ <state>
3
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
4
+ </state>
5
+ </component>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="GitToolBoxProjectSettings">
4
+ <option name="commitMessageIssueKeyValidationOverride">
5
+ <BoolValueOverride>
6
+ <option name="enabled" value="true" />
7
+ </BoolValueOverride>
8
+ </option>
9
+ <option name="commitMessageValidationEnabledOverride">
10
+ <BoolValueOverride>
11
+ <option name="enabled" value="true" />
12
+ </BoolValueOverride>
13
+ </option>
14
+ </component>
15
+ </project>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
+ </profile>
6
+ </component>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="JavaScriptLibraryMappings">
4
+ <includedPredefinedLibrary name="Node.js Core" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="PrettierConfiguration">
4
+ <option name="myConfigurationMode" value="AUTOMATIC" />
5
+ </component>
6
+ </project>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
package/.prettierrc ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "printWidth": 80,
3
+ "tabWidth": 2,
4
+ "singleQuote": true,
5
+ "trailingComma": "es5",
6
+ "semi": false,
7
+ "bracketSpacing": true,
8
+ "bracketSameLine": false,
9
+ "arrowParens": "avoid"
10
+ }
@@ -0,0 +1,234 @@
1
+ # Health Check Client
2
+
3
+ Provides health check endpoints for external monitoring services like BetterStack.
4
+ Uses a background worker to periodically check database connections and cache results in Redis,
5
+ preventing HTTP requests from triggering database queries.
6
+
7
+ ## How It Works
8
+
9
+ 1. **Worker Process**: Runs as a separate process (defined in Procfile)
10
+ - Performs health checks every minute (configurable)
11
+ - Checks database connections (and optionally Redis)
12
+ - Stores results in Redis cache with 60-second TTL
13
+ - Creates its own Redis connection with name `{appName}:healthcheck-worker`
14
+
15
+ 2. **Web Processes**: Handle HTTP requests
16
+ - Read health check results from Redis cache only
17
+ - Never trigger database queries
18
+ - Return cached data or error if Redis unavailable
19
+ - Use existing system Redis client
20
+
21
+ ## Environment Variables
22
+
23
+ | Variable | Description | Default |
24
+ |----------|-------------|---------|
25
+ | `BUILD_APP_NAME` | Application name for logging and Redis client naming | `unknown-app` |
26
+ | `HOSTNAME` | Dyno/instance ID for logging | `unknown-dyno` |
27
+ | `BUILD_DYNO_PROCESS_TYPE` | Process type for logging | `health-check-worker` |
28
+ | `HEALTH_LOG_VALUES` | Enable logging of health check refresh status (`true`/`false`) | `false` |
29
+ | `HEALTH_DB_MAX_CONNECT_LATENCY_MS` | Fail DB health if connection acquisition/connect time exceeds this (ms) | `1000` |
30
+ | `REDIS_URL` | Redis connection URL for cache | - |
31
+ | `DATABASE_URL` | Main PostgreSQL connection URL (for backend worker) | - |
32
+ | `META_DB_URL` | Main PostgreSQL connection URL (for database worker) | - |
33
+
34
+ ## Usage
35
+
36
+ ### Backend Service
37
+
38
+ **Endpoint Client** (`lib/healthCheck.ts`):
39
+ ```typescript
40
+ import { createHealthCheckEndpointClient } from '@adalo/metrics'
41
+ import { redisClient } from './redis'
42
+
43
+ export const healthCheckClient = createHealthCheckEndpointClient({
44
+ redisClient,
45
+ includeRedisCheck: true,
46
+ })
47
+ ```
48
+
49
+ **Worker** (`health-check-worker.ts`):
50
+ ```typescript
51
+ import { createHealthCheckWorker } from '@adalo/metrics'
52
+ import { createNamedRedisClient } from './lib/redis'
53
+
54
+ const healthCheckRedisClient = createNamedRedisClient('healthcheck-worker')
55
+
56
+ const runHealthCheckWorker = createHealthCheckWorker({
57
+ databaseUrl: process.env.DATABASE_URL,
58
+ databaseName: 'backend',
59
+ redisClient: healthCheckRedisClient,
60
+ includeRedisCheck: true,
61
+ })
62
+
63
+ runHealthCheckWorker().catch(err => {
64
+ console.error('[HealthCheckWorker] Fatal error:', err)
65
+ process.exit(1)
66
+ })
67
+ ```
68
+
69
+ **Procfile**:
70
+ ```
71
+ health-check-worker: node dist/health-check-worker.js
72
+ ```
73
+
74
+ ### Database Service
75
+
76
+ **Endpoint Client** (`lib/healthCheck.ts`):
77
+ ```typescript
78
+ import { createHealthCheckEndpointClient } from '@adalo/metrics'
79
+ import { redisClient } from './cache/client'
80
+
81
+ export const healthCheckClient = createHealthCheckEndpointClient({
82
+ redisClient,
83
+ })
84
+ ```
85
+
86
+ **Worker** (`workers/health-check-worker.ts`):
87
+ ```typescript
88
+ import { createHealthCheckWorker } from '@adalo/metrics'
89
+ import { getClusterDatabaseUrls } from '../utils/clusterUrls'
90
+ import { createNamedRedisClient } from '../lib/cache/client'
91
+
92
+ const healthCheckRedisClient = createNamedRedisClient('healthcheck-worker')
93
+
94
+ const runHealthCheckWorker = createHealthCheckWorker({
95
+ databaseUrl: process.env.META_DB_URL,
96
+ databaseName: 'meta_db',
97
+ additionalDatabaseUrls: getClusterDatabaseUrls(),
98
+ redisClient: healthCheckRedisClient,
99
+ })
100
+
101
+ runHealthCheckWorker().catch(err => {
102
+ console.error('[HealthCheckWorker] Fatal error:', err)
103
+ process.exit(1)
104
+ })
105
+ ```
106
+
107
+ **Procfile**:
108
+ ```
109
+ health-check-worker: node --enable-source-maps dist/workers/health-check-worker.js
110
+ ```
111
+
112
+ ## Endpoints
113
+
114
+ ### Default Endpoint
115
+
116
+ By default, `registerHealthEndpoint()` registers at `/health`:
117
+
118
+ ```typescript
119
+ healthCheckClient.registerHealthEndpoint(app)
120
+ // Registers: GET /health
121
+ ```
122
+
123
+ ### Custom Endpoints
124
+
125
+ You can also register custom endpoints that use the same cached data:
126
+
127
+ ```typescript
128
+ // Backend: /healthcheck (old endpoint, still works)
129
+ app.get('/healthcheck', healthCheckClient.healthHandler())
130
+
131
+ // Database: /hc (old endpoint, still works)
132
+ app.get('/hc', healthCheckClient.healthHandler())
133
+ ```
134
+
135
+ ## Response Format
136
+
137
+ ### Healthy Response
138
+ ```json
139
+ {
140
+ "status": "ok",
141
+ "lastCheckAt": 1738143477926,
142
+ "resources": {
143
+ "DATABASE_URL": { "status": "ok", "connectMs": 9.2 },
144
+ "REDIS_URL": { "status": "ok" }
145
+ },
146
+ "isStale": false,
147
+ "config": { "checkIntervalMs": 30000, "staleThresholdMs": 180000, "checkTimeoutMs": 15000 }
148
+ }
149
+ ```
150
+
151
+ ### Unhealthy Response (Redis Cache Unavailable)
152
+ ```json
153
+ {
154
+ "status": "unhealthy",
155
+ "timestamp": "2026-01-23T08:27:57.926Z",
156
+ "checks": {
157
+ "redis": {
158
+ "status": "unhealthy",
159
+ "error": "Redis cache read failed: connection refused"
160
+ }
161
+ },
162
+ "errors": ["Redis cache read failed: connection refused"]
163
+ }
164
+ ```
165
+
166
+ ### Unhealthy Response (Database Issue)
167
+ ```json
168
+ {
169
+ "status": "error",
170
+ "lastCheckAt": 1738143477926,
171
+ "resources": {
172
+ "DATABASE_URL": { "status": "error", "error": "connection timeout", "connectMs": 5000 }
173
+ },
174
+ "isStale": false,
175
+ "config": { "checkIntervalMs": 30000, "staleThresholdMs": 180000, "checkTimeoutMs": 15000 }
176
+ }
177
+ ```
178
+
179
+ ## Status Codes
180
+
181
+ - `200` - healthy or degraded
182
+ - `503` - unhealthy (at least one component failed)
183
+
184
+ ## Configuration Options
185
+
186
+ ### Worker Options
187
+
188
+ ```typescript
189
+ createHealthCheckWorker({
190
+ databaseUrl: string, // Required: Main database URL
191
+ databaseName: string, // Required: Database name
192
+ redisClient: any, // Required: Redis client instance (created with createNamedRedisClient('healthcheck-worker'))
193
+ additionalDatabaseUrls?: object, // Optional: Additional clusters (database service)
194
+ includeRedisCheck?: boolean, // Optional: Include Redis in health check (default: false)
195
+ refreshIntervalMs?: number, // Optional: Refresh interval (default: 30000ms)
196
+ appName?: string, // Optional: App name (defaults to BUILD_APP_NAME)
197
+ })
198
+ ```
199
+
200
+ ### Endpoint Client Options
201
+
202
+ ```typescript
203
+ createHealthCheckEndpointClient({
204
+ redisClient: any, // Required: Existing Redis client from system
205
+ includeRedisCheck?: boolean, // Optional: Include Redis in response (default: false)
206
+ })
207
+ ```
208
+
209
+ ## Logging Format
210
+
211
+ Worker logs follow the same format as metrics:
212
+
213
+ ```
214
+ [health-check-worker] [app-name] [dyno-id] [HealthCheck] Starting health check worker...
215
+ [health-check-worker] [app-name] [dyno-id] [HealthCheck] Refresh interval: 60000ms
216
+ ```
217
+
218
+ When `HEALTH_LOG_VALUES=true`, additional logs are printed:
219
+
220
+ ```
221
+ [health-check-worker] [app-name] [dyno-id] [HealthCheck] Initial health check completed
222
+ [health-check-worker] [app-name] [dyno-id] [HealthCheck] Health check refreshed at 2026-01-23T08:27:57.926Z, status: healthy
223
+ ```
224
+
225
+ Error logs are always printed regardless of `HEALTH_LOG_VALUES` setting.
226
+
227
+ ## Benefits
228
+
229
+ - **No Concurrent DB Queries**: Only worker queries database, endpoints read from cache
230
+ - **Same Data Per Minute**: All requests get the same cached result within the TTL period
231
+ - **Redis Optional**: Works even if Redis is disabled (falls back to in-memory cache per process)
232
+ - **Graceful Degradation**: Returns proper error format if Redis unavailable
233
+ - **Multi-Process Support**: Shared cache across all web process instances via Redis
234
+