@checkmate-monitor/healthcheck-backend 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,121 @@
1
+ # @checkmate-monitor/healthcheck-backend
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ae19ff6: Add configurable state thresholds for health check evaluation
8
+
9
+ **@checkmate-monitor/backend-api:**
10
+
11
+ - Added `VersionedData<T>` generic interface as base for all versioned data structures
12
+ - `VersionedConfig<T>` now extends `VersionedData<T>` and adds `pluginId`
13
+ - Added `migrateVersionedData()` utility function for running migrations on any `VersionedData` subtype
14
+
15
+ **@checkmate-monitor/backend:**
16
+
17
+ - Refactored `ConfigMigrationRunner` to use the new `migrateVersionedData` utility
18
+
19
+ **@checkmate-monitor/healthcheck-common:**
20
+
21
+ - Added state threshold schemas with two evaluation modes (consecutive, window)
22
+ - Added `stateThresholds` field to `AssociateHealthCheckSchema`
23
+ - Added `getSystemHealthStatus` RPC endpoint contract
24
+
25
+ **@checkmate-monitor/healthcheck-backend:**
26
+
27
+ - Added `stateThresholds` column to `system_health_checks` table
28
+ - Added `state-evaluator.ts` with health status evaluation logic
29
+ - Added `state-thresholds-migrations.ts` with migration infrastructure
30
+ - Added `getSystemHealthStatus` RPC handler
31
+
32
+ **@checkmate-monitor/healthcheck-frontend:**
33
+
34
+ - Updated `SystemHealthBadge` to use new backend endpoint
35
+
36
+ - 0babb9c: Add public health status access and detailed history for admins
37
+
38
+ **Permission changes:**
39
+
40
+ - Added `healthcheck.status.read` permission with `isPublicDefault: true` for anonymous access
41
+ - `getSystemHealthStatus`, `getSystemHealthOverview`, and `getHistory` now public
42
+ - `getHistory` no longer returns `result` field (security)
43
+
44
+ **New features:**
45
+
46
+ - Added `getDetailedHistory` endpoint with `healthcheck.manage` permission
47
+ - New `/healthcheck/history` page showing paginated run history with expandable result JSON
48
+
49
+ ### Patch Changes
50
+
51
+ - e4d83fc: Add BullMQ queue plugin with orphaned job cleanup
52
+
53
+ - **queue-api**: Added `listRecurringJobs()` method to Queue interface for detecting orphaned jobs
54
+ - **queue-bullmq-backend**: New plugin implementing BullMQ (Redis) queue backend with job schedulers, consumer groups, and distributed job persistence
55
+ - **queue-bullmq-common**: New common package with queue permissions
56
+ - **queue-memory-backend**: Implemented `listRecurringJobs()` for in-memory queue
57
+ - **healthcheck-backend**: Enhanced `bootstrapHealthChecks` to clean up orphaned job schedulers using `listRecurringJobs()`
58
+ - **test-utils-backend**: Added `listRecurringJobs()` to mock queue factory
59
+
60
+ This enables production-ready distributed queue processing with Redis persistence and automatic cleanup of orphaned jobs when health checks are deleted.
61
+
62
+ - 81f3f85: ## Breaking: Unified Versioned<T> Architecture
63
+
64
+ Refactored the versioning system to use a unified `Versioned<T>` class instead of separate `VersionedSchema`, `VersionedData`, and `VersionedConfig` types.
65
+
66
+ ### Breaking Changes
67
+
68
+ - **`VersionedSchema<T>`** is replaced by `Versioned<T>` class
69
+ - **`VersionedData<T>`** is replaced by `VersionedRecord<T>` interface
70
+ - **`VersionedConfig<T>`** is replaced by `VersionedPluginRecord<T>` interface
71
+ - **`ConfigMigration<F, T>`** is replaced by `Migration<F, T>` interface
72
+ - **`MigrationChain<T>`** is removed (use `Migration<unknown, unknown>[]`)
73
+ - **`migrateVersionedData()`** is removed (use `versioned.parse()`)
74
+ - **`ConfigMigrationRunner`** is removed (migrations are internal to Versioned)
75
+
76
+ ### Migration Guide
77
+
78
+ Before:
79
+
80
+ ```typescript
81
+ const strategy: HealthCheckStrategy = {
82
+ config: {
83
+ version: 1,
84
+ schema: mySchema,
85
+ migrations: [],
86
+ },
87
+ };
88
+ const data = await migrateVersionedData(stored, 1, migrations);
89
+ ```
90
+
91
+ After:
92
+
93
+ ```typescript
94
+ const strategy: HealthCheckStrategy = {
95
+ config: new Versioned({
96
+ version: 1,
97
+ schema: mySchema,
98
+ migrations: [],
99
+ }),
100
+ };
101
+ const data = await strategy.config.parse(stored);
102
+ ```
103
+
104
+ - Updated dependencies [ffc28f6]
105
+ - Updated dependencies [e4d83fc]
106
+ - Updated dependencies [4dd644d]
107
+ - Updated dependencies [71275dd]
108
+ - Updated dependencies [ae19ff6]
109
+ - Updated dependencies [0babb9c]
110
+ - Updated dependencies [b55fae6]
111
+ - Updated dependencies [b354ab3]
112
+ - Updated dependencies [8e889b4]
113
+ - Updated dependencies [81f3f85]
114
+ - @checkmate-monitor/common@0.1.0
115
+ - @checkmate-monitor/backend-api@1.0.0
116
+ - @checkmate-monitor/catalog-common@0.1.0
117
+ - @checkmate-monitor/queue-api@1.0.0
118
+ - @checkmate-monitor/healthcheck-common@0.1.0
119
+ - @checkmate-monitor/signal-common@0.1.0
120
+ - @checkmate-monitor/catalog-backend@0.0.2
121
+ - @checkmate-monitor/integration-backend@0.0.2
@@ -0,0 +1,33 @@
1
+ CREATE TYPE "health_check_status" AS ENUM('healthy', 'unhealthy', 'degraded');--> statement-breakpoint
2
+ CREATE TABLE "health_check_configurations" (
3
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
4
+ "name" text NOT NULL,
5
+ "strategy_id" text NOT NULL,
6
+ "config" jsonb NOT NULL,
7
+ "interval_seconds" integer NOT NULL,
8
+ "is_template" boolean DEFAULT false,
9
+ "created_at" timestamp DEFAULT now() NOT NULL,
10
+ "updated_at" timestamp DEFAULT now() NOT NULL
11
+ );
12
+ --> statement-breakpoint
13
+ CREATE TABLE "health_check_runs" (
14
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
15
+ "configuration_id" uuid NOT NULL,
16
+ "system_id" text NOT NULL,
17
+ "status" "health_check_status" NOT NULL,
18
+ "result" jsonb,
19
+ "timestamp" timestamp DEFAULT now() NOT NULL
20
+ );
21
+ --> statement-breakpoint
22
+ CREATE TABLE "system_health_checks" (
23
+ "system_id" text NOT NULL,
24
+ "configuration_id" uuid NOT NULL,
25
+ "enabled" boolean DEFAULT true NOT NULL,
26
+ "state_thresholds" jsonb,
27
+ "created_at" timestamp DEFAULT now() NOT NULL,
28
+ "updated_at" timestamp DEFAULT now() NOT NULL,
29
+ CONSTRAINT "system_health_checks_system_id_configuration_id_pk" PRIMARY KEY("system_id","configuration_id")
30
+ );
31
+ --> statement-breakpoint
32
+ ALTER TABLE "health_check_runs" ADD CONSTRAINT "health_check_runs_configuration_id_health_check_configurations_id_fk" FOREIGN KEY ("configuration_id") REFERENCES "health_check_configurations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
33
+ ALTER TABLE "system_health_checks" ADD CONSTRAINT "system_health_checks_configuration_id_health_check_configurations_id_fk" FOREIGN KEY ("configuration_id") REFERENCES "health_check_configurations"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1 @@
1
+ ALTER TABLE "health_check_runs" ADD COLUMN "latency_ms" integer;
@@ -0,0 +1,19 @@
1
+ CREATE TYPE "bucket_size" AS ENUM('hourly', 'daily');--> statement-breakpoint
2
+ CREATE TABLE "health_check_aggregates" (
3
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
4
+ "configuration_id" uuid NOT NULL,
5
+ "system_id" text NOT NULL,
6
+ "bucket_start" timestamp NOT NULL,
7
+ "bucket_size" "bucket_size" NOT NULL,
8
+ "run_count" integer NOT NULL,
9
+ "healthy_count" integer NOT NULL,
10
+ "degraded_count" integer NOT NULL,
11
+ "unhealthy_count" integer NOT NULL,
12
+ "avg_latency_ms" integer,
13
+ "min_latency_ms" integer,
14
+ "max_latency_ms" integer,
15
+ "p95_latency_ms" integer,
16
+ "aggregated_metadata" jsonb
17
+ );
18
+ --> statement-breakpoint
19
+ ALTER TABLE "health_check_aggregates" ADD CONSTRAINT "health_check_aggregates_configuration_id_health_check_configurations_id_fk" FOREIGN KEY ("configuration_id") REFERENCES "health_check_configurations"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1 @@
1
+ ALTER TABLE "system_health_checks" ADD COLUMN "retention_config" jsonb;
@@ -0,0 +1 @@
1
+ CREATE UNIQUE INDEX "health_check_aggregates_bucket_unique" ON "health_check_aggregates" USING btree ("configuration_id","system_id","bucket_start","bucket_size");
@@ -0,0 +1 @@
1
+ ALTER TABLE "health_check_aggregates" RENAME COLUMN "aggregated_metadata" TO "aggregated_result";
@@ -0,0 +1,234 @@
1
+ {
2
+ "id": "74a6fab2-e8df-4d0f-a2af-669d072ecdf5",
3
+ "prevId": "00000000-0000-0000-0000-000000000000",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.health_check_configurations": {
8
+ "name": "health_check_configurations",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "uuid",
14
+ "primaryKey": true,
15
+ "notNull": true,
16
+ "default": "gen_random_uuid()"
17
+ },
18
+ "name": {
19
+ "name": "name",
20
+ "type": "text",
21
+ "primaryKey": false,
22
+ "notNull": true
23
+ },
24
+ "strategy_id": {
25
+ "name": "strategy_id",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true
29
+ },
30
+ "config": {
31
+ "name": "config",
32
+ "type": "jsonb",
33
+ "primaryKey": false,
34
+ "notNull": true
35
+ },
36
+ "interval_seconds": {
37
+ "name": "interval_seconds",
38
+ "type": "integer",
39
+ "primaryKey": false,
40
+ "notNull": true
41
+ },
42
+ "is_template": {
43
+ "name": "is_template",
44
+ "type": "boolean",
45
+ "primaryKey": false,
46
+ "notNull": false,
47
+ "default": false
48
+ },
49
+ "created_at": {
50
+ "name": "created_at",
51
+ "type": "timestamp",
52
+ "primaryKey": false,
53
+ "notNull": true,
54
+ "default": "now()"
55
+ },
56
+ "updated_at": {
57
+ "name": "updated_at",
58
+ "type": "timestamp",
59
+ "primaryKey": false,
60
+ "notNull": true,
61
+ "default": "now()"
62
+ }
63
+ },
64
+ "indexes": {},
65
+ "foreignKeys": {},
66
+ "compositePrimaryKeys": {},
67
+ "uniqueConstraints": {},
68
+ "policies": {},
69
+ "checkConstraints": {},
70
+ "isRLSEnabled": false
71
+ },
72
+ "public.health_check_runs": {
73
+ "name": "health_check_runs",
74
+ "schema": "",
75
+ "columns": {
76
+ "id": {
77
+ "name": "id",
78
+ "type": "uuid",
79
+ "primaryKey": true,
80
+ "notNull": true,
81
+ "default": "gen_random_uuid()"
82
+ },
83
+ "configuration_id": {
84
+ "name": "configuration_id",
85
+ "type": "uuid",
86
+ "primaryKey": false,
87
+ "notNull": true
88
+ },
89
+ "system_id": {
90
+ "name": "system_id",
91
+ "type": "text",
92
+ "primaryKey": false,
93
+ "notNull": true
94
+ },
95
+ "status": {
96
+ "name": "status",
97
+ "type": "health_check_status",
98
+ "typeSchema": "public",
99
+ "primaryKey": false,
100
+ "notNull": true
101
+ },
102
+ "result": {
103
+ "name": "result",
104
+ "type": "jsonb",
105
+ "primaryKey": false,
106
+ "notNull": false
107
+ },
108
+ "timestamp": {
109
+ "name": "timestamp",
110
+ "type": "timestamp",
111
+ "primaryKey": false,
112
+ "notNull": true,
113
+ "default": "now()"
114
+ }
115
+ },
116
+ "indexes": {},
117
+ "foreignKeys": {
118
+ "health_check_runs_configuration_id_health_check_configurations_id_fk": {
119
+ "name": "health_check_runs_configuration_id_health_check_configurations_id_fk",
120
+ "tableFrom": "health_check_runs",
121
+ "tableTo": "health_check_configurations",
122
+ "columnsFrom": [
123
+ "configuration_id"
124
+ ],
125
+ "columnsTo": [
126
+ "id"
127
+ ],
128
+ "onDelete": "cascade",
129
+ "onUpdate": "no action"
130
+ }
131
+ },
132
+ "compositePrimaryKeys": {},
133
+ "uniqueConstraints": {},
134
+ "policies": {},
135
+ "checkConstraints": {},
136
+ "isRLSEnabled": false
137
+ },
138
+ "public.system_health_checks": {
139
+ "name": "system_health_checks",
140
+ "schema": "",
141
+ "columns": {
142
+ "system_id": {
143
+ "name": "system_id",
144
+ "type": "text",
145
+ "primaryKey": false,
146
+ "notNull": true
147
+ },
148
+ "configuration_id": {
149
+ "name": "configuration_id",
150
+ "type": "uuid",
151
+ "primaryKey": false,
152
+ "notNull": true
153
+ },
154
+ "enabled": {
155
+ "name": "enabled",
156
+ "type": "boolean",
157
+ "primaryKey": false,
158
+ "notNull": true,
159
+ "default": true
160
+ },
161
+ "state_thresholds": {
162
+ "name": "state_thresholds",
163
+ "type": "jsonb",
164
+ "primaryKey": false,
165
+ "notNull": false
166
+ },
167
+ "created_at": {
168
+ "name": "created_at",
169
+ "type": "timestamp",
170
+ "primaryKey": false,
171
+ "notNull": true,
172
+ "default": "now()"
173
+ },
174
+ "updated_at": {
175
+ "name": "updated_at",
176
+ "type": "timestamp",
177
+ "primaryKey": false,
178
+ "notNull": true,
179
+ "default": "now()"
180
+ }
181
+ },
182
+ "indexes": {},
183
+ "foreignKeys": {
184
+ "system_health_checks_configuration_id_health_check_configurations_id_fk": {
185
+ "name": "system_health_checks_configuration_id_health_check_configurations_id_fk",
186
+ "tableFrom": "system_health_checks",
187
+ "tableTo": "health_check_configurations",
188
+ "columnsFrom": [
189
+ "configuration_id"
190
+ ],
191
+ "columnsTo": [
192
+ "id"
193
+ ],
194
+ "onDelete": "cascade",
195
+ "onUpdate": "no action"
196
+ }
197
+ },
198
+ "compositePrimaryKeys": {
199
+ "system_health_checks_system_id_configuration_id_pk": {
200
+ "name": "system_health_checks_system_id_configuration_id_pk",
201
+ "columns": [
202
+ "system_id",
203
+ "configuration_id"
204
+ ]
205
+ }
206
+ },
207
+ "uniqueConstraints": {},
208
+ "policies": {},
209
+ "checkConstraints": {},
210
+ "isRLSEnabled": false
211
+ }
212
+ },
213
+ "enums": {
214
+ "public.health_check_status": {
215
+ "name": "health_check_status",
216
+ "schema": "public",
217
+ "values": [
218
+ "healthy",
219
+ "unhealthy",
220
+ "degraded"
221
+ ]
222
+ }
223
+ },
224
+ "schemas": {},
225
+ "sequences": {},
226
+ "roles": {},
227
+ "policies": {},
228
+ "views": {},
229
+ "_meta": {
230
+ "columns": {},
231
+ "schemas": {},
232
+ "tables": {}
233
+ }
234
+ }
@@ -0,0 +1,240 @@
1
+ {
2
+ "id": "2dab14f4-bacc-4f60-845b-c7c27ce740d0",
3
+ "prevId": "74a6fab2-e8df-4d0f-a2af-669d072ecdf5",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.health_check_configurations": {
8
+ "name": "health_check_configurations",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "uuid",
14
+ "primaryKey": true,
15
+ "notNull": true,
16
+ "default": "gen_random_uuid()"
17
+ },
18
+ "name": {
19
+ "name": "name",
20
+ "type": "text",
21
+ "primaryKey": false,
22
+ "notNull": true
23
+ },
24
+ "strategy_id": {
25
+ "name": "strategy_id",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true
29
+ },
30
+ "config": {
31
+ "name": "config",
32
+ "type": "jsonb",
33
+ "primaryKey": false,
34
+ "notNull": true
35
+ },
36
+ "interval_seconds": {
37
+ "name": "interval_seconds",
38
+ "type": "integer",
39
+ "primaryKey": false,
40
+ "notNull": true
41
+ },
42
+ "is_template": {
43
+ "name": "is_template",
44
+ "type": "boolean",
45
+ "primaryKey": false,
46
+ "notNull": false,
47
+ "default": false
48
+ },
49
+ "created_at": {
50
+ "name": "created_at",
51
+ "type": "timestamp",
52
+ "primaryKey": false,
53
+ "notNull": true,
54
+ "default": "now()"
55
+ },
56
+ "updated_at": {
57
+ "name": "updated_at",
58
+ "type": "timestamp",
59
+ "primaryKey": false,
60
+ "notNull": true,
61
+ "default": "now()"
62
+ }
63
+ },
64
+ "indexes": {},
65
+ "foreignKeys": {},
66
+ "compositePrimaryKeys": {},
67
+ "uniqueConstraints": {},
68
+ "policies": {},
69
+ "checkConstraints": {},
70
+ "isRLSEnabled": false
71
+ },
72
+ "public.health_check_runs": {
73
+ "name": "health_check_runs",
74
+ "schema": "",
75
+ "columns": {
76
+ "id": {
77
+ "name": "id",
78
+ "type": "uuid",
79
+ "primaryKey": true,
80
+ "notNull": true,
81
+ "default": "gen_random_uuid()"
82
+ },
83
+ "configuration_id": {
84
+ "name": "configuration_id",
85
+ "type": "uuid",
86
+ "primaryKey": false,
87
+ "notNull": true
88
+ },
89
+ "system_id": {
90
+ "name": "system_id",
91
+ "type": "text",
92
+ "primaryKey": false,
93
+ "notNull": true
94
+ },
95
+ "status": {
96
+ "name": "status",
97
+ "type": "health_check_status",
98
+ "typeSchema": "public",
99
+ "primaryKey": false,
100
+ "notNull": true
101
+ },
102
+ "latency_ms": {
103
+ "name": "latency_ms",
104
+ "type": "integer",
105
+ "primaryKey": false,
106
+ "notNull": false
107
+ },
108
+ "result": {
109
+ "name": "result",
110
+ "type": "jsonb",
111
+ "primaryKey": false,
112
+ "notNull": false
113
+ },
114
+ "timestamp": {
115
+ "name": "timestamp",
116
+ "type": "timestamp",
117
+ "primaryKey": false,
118
+ "notNull": true,
119
+ "default": "now()"
120
+ }
121
+ },
122
+ "indexes": {},
123
+ "foreignKeys": {
124
+ "health_check_runs_configuration_id_health_check_configurations_id_fk": {
125
+ "name": "health_check_runs_configuration_id_health_check_configurations_id_fk",
126
+ "tableFrom": "health_check_runs",
127
+ "tableTo": "health_check_configurations",
128
+ "columnsFrom": [
129
+ "configuration_id"
130
+ ],
131
+ "columnsTo": [
132
+ "id"
133
+ ],
134
+ "onDelete": "cascade",
135
+ "onUpdate": "no action"
136
+ }
137
+ },
138
+ "compositePrimaryKeys": {},
139
+ "uniqueConstraints": {},
140
+ "policies": {},
141
+ "checkConstraints": {},
142
+ "isRLSEnabled": false
143
+ },
144
+ "public.system_health_checks": {
145
+ "name": "system_health_checks",
146
+ "schema": "",
147
+ "columns": {
148
+ "system_id": {
149
+ "name": "system_id",
150
+ "type": "text",
151
+ "primaryKey": false,
152
+ "notNull": true
153
+ },
154
+ "configuration_id": {
155
+ "name": "configuration_id",
156
+ "type": "uuid",
157
+ "primaryKey": false,
158
+ "notNull": true
159
+ },
160
+ "enabled": {
161
+ "name": "enabled",
162
+ "type": "boolean",
163
+ "primaryKey": false,
164
+ "notNull": true,
165
+ "default": true
166
+ },
167
+ "state_thresholds": {
168
+ "name": "state_thresholds",
169
+ "type": "jsonb",
170
+ "primaryKey": false,
171
+ "notNull": false
172
+ },
173
+ "created_at": {
174
+ "name": "created_at",
175
+ "type": "timestamp",
176
+ "primaryKey": false,
177
+ "notNull": true,
178
+ "default": "now()"
179
+ },
180
+ "updated_at": {
181
+ "name": "updated_at",
182
+ "type": "timestamp",
183
+ "primaryKey": false,
184
+ "notNull": true,
185
+ "default": "now()"
186
+ }
187
+ },
188
+ "indexes": {},
189
+ "foreignKeys": {
190
+ "system_health_checks_configuration_id_health_check_configurations_id_fk": {
191
+ "name": "system_health_checks_configuration_id_health_check_configurations_id_fk",
192
+ "tableFrom": "system_health_checks",
193
+ "tableTo": "health_check_configurations",
194
+ "columnsFrom": [
195
+ "configuration_id"
196
+ ],
197
+ "columnsTo": [
198
+ "id"
199
+ ],
200
+ "onDelete": "cascade",
201
+ "onUpdate": "no action"
202
+ }
203
+ },
204
+ "compositePrimaryKeys": {
205
+ "system_health_checks_system_id_configuration_id_pk": {
206
+ "name": "system_health_checks_system_id_configuration_id_pk",
207
+ "columns": [
208
+ "system_id",
209
+ "configuration_id"
210
+ ]
211
+ }
212
+ },
213
+ "uniqueConstraints": {},
214
+ "policies": {},
215
+ "checkConstraints": {},
216
+ "isRLSEnabled": false
217
+ }
218
+ },
219
+ "enums": {
220
+ "public.health_check_status": {
221
+ "name": "health_check_status",
222
+ "schema": "public",
223
+ "values": [
224
+ "healthy",
225
+ "unhealthy",
226
+ "degraded"
227
+ ]
228
+ }
229
+ },
230
+ "schemas": {},
231
+ "sequences": {},
232
+ "roles": {},
233
+ "policies": {},
234
+ "views": {},
235
+ "_meta": {
236
+ "columns": {},
237
+ "schemas": {},
238
+ "tables": {}
239
+ }
240
+ }