@checkstack/healthcheck-common 0.0.2

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,88 @@
1
+ # @checkstack/healthcheck-common
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
8
+ - Updated dependencies [d20d274]
9
+ - @checkstack/common@0.0.2
10
+ - @checkstack/signal-common@0.0.2
11
+
12
+ ## 0.1.1
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [a65e002]
17
+ - @checkstack/common@0.2.0
18
+ - @checkstack/signal-common@0.1.1
19
+
20
+ ## 0.1.0
21
+
22
+ ### Minor Changes
23
+
24
+ - 4dd644d: Enable external application (API key) access to management endpoints
25
+
26
+ Changed `userType: "user"` to `userType: "authenticated"` for 52 endpoints across 5 packages, allowing external applications (service accounts with API keys) to call these endpoints programmatically while maintaining RBAC permission checks:
27
+
28
+ - **incident-common**: createIncident, updateIncident, addUpdate, resolveIncident, deleteIncident
29
+ - **maintenance-common**: createMaintenance, updateMaintenance, addUpdate, closeMaintenance, deleteMaintenance
30
+ - **catalog-common**: System CRUD, Group CRUD, addSystemToGroup, removeSystemFromGroup
31
+ - **healthcheck-common**: Configuration management, system associations, retention config, detailed history
32
+ - **integration-common**: Subscription management, connection management, event discovery, delivery logs
33
+
34
+ This enables automation use cases such as:
35
+
36
+ - Creating incidents from external monitoring systems (Prometheus, Grafana)
37
+ - Scheduling maintenances from CI/CD pipelines
38
+ - Managing catalog systems from infrastructure-as-code tools
39
+ - Configuring health checks from deployment scripts
40
+
41
+ - ae19ff6: Add configurable state thresholds for health check evaluation
42
+
43
+ **@checkstack/backend-api:**
44
+
45
+ - Added `VersionedData<T>` generic interface as base for all versioned data structures
46
+ - `VersionedConfig<T>` now extends `VersionedData<T>` and adds `pluginId`
47
+ - Added `migrateVersionedData()` utility function for running migrations on any `VersionedData` subtype
48
+
49
+ **@checkstack/backend:**
50
+
51
+ - Refactored `ConfigMigrationRunner` to use the new `migrateVersionedData` utility
52
+
53
+ **@checkstack/healthcheck-common:**
54
+
55
+ - Added state threshold schemas with two evaluation modes (consecutive, window)
56
+ - Added `stateThresholds` field to `AssociateHealthCheckSchema`
57
+ - Added `getSystemHealthStatus` RPC endpoint contract
58
+
59
+ **@checkstack/healthcheck-backend:**
60
+
61
+ - Added `stateThresholds` column to `system_health_checks` table
62
+ - Added `state-evaluator.ts` with health status evaluation logic
63
+ - Added `state-thresholds-migrations.ts` with migration infrastructure
64
+ - Added `getSystemHealthStatus` RPC handler
65
+
66
+ **@checkstack/healthcheck-frontend:**
67
+
68
+ - Updated `SystemHealthBadge` to use new backend endpoint
69
+
70
+ - 0babb9c: Add public health status access and detailed history for admins
71
+
72
+ **Permission changes:**
73
+
74
+ - Added `healthcheck.status.read` permission with `isPublicDefault: true` for anonymous access
75
+ - `getSystemHealthStatus`, `getSystemHealthOverview`, and `getHistory` now public
76
+ - `getHistory` no longer returns `result` field (security)
77
+
78
+ **New features:**
79
+
80
+ - Added `getDetailedHistory` endpoint with `healthcheck.manage` permission
81
+ - New `/healthcheck/history` page showing paginated run history with expandable result JSON
82
+
83
+ ### Patch Changes
84
+
85
+ - Updated dependencies [ffc28f6]
86
+ - Updated dependencies [b55fae6]
87
+ - @checkstack/common@0.1.0
88
+ - @checkstack/signal-common@0.1.0
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@checkstack/healthcheck-common",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./src/index.ts"
8
+ }
9
+ },
10
+ "dependencies": {
11
+ "@checkstack/common": "workspace:*",
12
+ "@checkstack/signal-common": "workspace:*",
13
+ "zod": "^4.2.1"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.7.2",
17
+ "@checkstack/tsconfig": "workspace:*",
18
+ "@checkstack/scripts": "workspace:*"
19
+ },
20
+ "scripts": {
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "bun run lint:code",
23
+ "lint:code": "eslint . --max-warnings 0"
24
+ }
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ export * from "./permissions";
2
+ export * from "./schemas";
3
+ export * from "./zod-health-result";
4
+
5
+ // --- DTOs for API Responses ---
6
+
7
+ /**
8
+ * Represents a Health Check Strategy available in the system.
9
+ */
10
+ export interface HealthCheckStrategyDto {
11
+ id: string;
12
+ displayName: string;
13
+ description?: string;
14
+ // schema is a JSON schema object derived from the Zod schema
15
+ configSchema: Record<string, unknown>;
16
+ }
17
+
18
+ /**
19
+ * Represents a Health Check Configuration (the check definition/template).
20
+ * NOTE: This is derived from Zod schema but kept as interface for explicit type documentation.
21
+ */
22
+ export interface HealthCheckConfiguration {
23
+ id: string;
24
+ name: string;
25
+ strategyId: string;
26
+ config: Record<string, unknown>;
27
+ intervalSeconds: number;
28
+ createdAt: Date;
29
+ updatedAt: Date;
30
+ }
31
+
32
+ // HealthCheckRun and HealthCheckStatus types are now exported from ./schemas
33
+
34
+ export * from "./rpc-contract";
35
+ export * from "./plugin-metadata";
36
+ export { healthcheckRoutes } from "./routes";
37
+
38
+ // =============================================================================
39
+ // REALTIME SIGNALS
40
+ // =============================================================================
41
+
42
+ import { createSignal } from "@checkstack/signal-common";
43
+ import { z } from "zod";
44
+
45
+ /**
46
+ * Broadcast when a health check run completes.
47
+ * Frontend components listening to this signal can update live activity feeds.
48
+ */
49
+ export const HEALTH_CHECK_RUN_COMPLETED = createSignal(
50
+ "healthcheck.run.completed",
51
+ z.object({
52
+ systemId: z.string(),
53
+ systemName: z.string(),
54
+ configurationId: z.string(),
55
+ configurationName: z.string(),
56
+ status: z.enum(["healthy", "degraded", "unhealthy"]),
57
+ latencyMs: z.number().optional(),
58
+ })
59
+ );
@@ -0,0 +1,39 @@
1
+ import { createPermission } from "@checkstack/common";
2
+
3
+ export const permissions = {
4
+ /**
5
+ * Status-only permission for viewing health check status.
6
+ * Enabled by default for anonymous and authenticated users.
7
+ */
8
+ healthCheckStatusRead: createPermission(
9
+ "healthcheck.status",
10
+ "read",
11
+ "View Health Check Status",
12
+ { isAuthenticatedDefault: true, isPublicDefault: true }
13
+ ),
14
+ /**
15
+ * Configuration read permission for viewing health check configurations.
16
+ * Restricted to users with explicit grant.
17
+ */
18
+ healthCheckRead: createPermission(
19
+ "healthcheck",
20
+ "read",
21
+ "Read Health Check Configurations"
22
+ ),
23
+ /**
24
+ * Permission for viewing detailed health check run data including metadata.
25
+ * Allows access to extended visualizations without full management access.
26
+ */
27
+ healthCheckDetailsRead: createPermission(
28
+ "healthcheck.details",
29
+ "read",
30
+ "View Detailed Health Check Run Data (Warning: This may expose sensitive data, depending on the health check strategy)"
31
+ ),
32
+ healthCheckManage: createPermission(
33
+ "healthcheck",
34
+ "manage",
35
+ "Full management of Health Check Configurations"
36
+ ),
37
+ };
38
+
39
+ export const permissionList = Object.values(permissions);
@@ -0,0 +1,9 @@
1
+ import { definePluginMetadata } from "@checkstack/common";
2
+
3
+ /**
4
+ * Plugin metadata for the healthcheck plugin.
5
+ * Exported from the common package so both backend and frontend can reference it.
6
+ */
7
+ export const pluginMetadata = definePluginMetadata({
8
+ pluginId: "healthcheck",
9
+ });
package/src/routes.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { createRoutes } from "@checkstack/common";
2
+
3
+ /**
4
+ * Route definitions for the healthcheck plugin.
5
+ */
6
+ export const healthcheckRoutes = createRoutes("healthcheck", {
7
+ config: "/config",
8
+ history: "/history",
9
+ historyDetail: "/history/:systemId/:configurationId",
10
+ });
@@ -0,0 +1,347 @@
1
+ import { oc } from "@orpc/contract";
2
+ import {
3
+ createClientDefinition,
4
+ type ProcedureMetadata,
5
+ } from "@checkstack/common";
6
+ import { pluginMetadata } from "./plugin-metadata";
7
+ import { z } from "zod";
8
+ import { permissions } from "./permissions";
9
+ import {
10
+ HealthCheckStrategyDtoSchema,
11
+ HealthCheckConfigurationSchema,
12
+ CreateHealthCheckConfigurationSchema,
13
+ UpdateHealthCheckConfigurationSchema,
14
+ AssociateHealthCheckSchema,
15
+ HealthCheckRunSchema,
16
+ HealthCheckRunPublicSchema,
17
+ HealthCheckStatusSchema,
18
+ StateThresholdsSchema,
19
+ RetentionConfigSchema,
20
+ AggregatedBucketBaseSchema,
21
+ AggregatedBucketSchema,
22
+ } from "./schemas";
23
+
24
+ // Base builder with full metadata support
25
+ const _base = oc.$meta<ProcedureMetadata>({});
26
+
27
+ // --- Response Schemas for Evaluated Status ---
28
+
29
+ const SystemCheckStatusSchema = z.object({
30
+ configurationId: z.string(),
31
+ configurationName: z.string(),
32
+ status: HealthCheckStatusSchema,
33
+ runsConsidered: z.number(),
34
+ lastRunAt: z.date().optional(),
35
+ });
36
+
37
+ const SystemHealthStatusResponseSchema = z.object({
38
+ status: HealthCheckStatusSchema,
39
+ evaluatedAt: z.date(),
40
+ checkStatuses: z.array(SystemCheckStatusSchema),
41
+ });
42
+
43
+ // Health Check RPC Contract using oRPC's contract-first pattern
44
+ export const healthCheckContract = {
45
+ // ==========================================================================
46
+ // STRATEGY MANAGEMENT (userType: "authenticated" with read permission)
47
+ // ==========================================================================
48
+
49
+ getStrategies: _base
50
+ .meta({
51
+ userType: "authenticated",
52
+ permissions: [permissions.healthCheckRead.id],
53
+ })
54
+ .output(z.array(HealthCheckStrategyDtoSchema)),
55
+
56
+ // ==========================================================================
57
+ // CONFIGURATION MANAGEMENT (userType: "authenticated")
58
+ // ==========================================================================
59
+
60
+ getConfigurations: _base
61
+ .meta({
62
+ userType: "authenticated",
63
+ permissions: [permissions.healthCheckRead.id],
64
+ })
65
+ .output(z.array(HealthCheckConfigurationSchema)),
66
+
67
+ createConfiguration: _base
68
+ .meta({
69
+ userType: "authenticated",
70
+ permissions: [permissions.healthCheckManage.id],
71
+ })
72
+ .input(CreateHealthCheckConfigurationSchema)
73
+ .output(HealthCheckConfigurationSchema),
74
+
75
+ updateConfiguration: _base
76
+ .meta({
77
+ userType: "authenticated",
78
+ permissions: [permissions.healthCheckManage.id],
79
+ })
80
+ .input(
81
+ z.object({
82
+ id: z.string(),
83
+ body: UpdateHealthCheckConfigurationSchema,
84
+ })
85
+ )
86
+ .output(HealthCheckConfigurationSchema),
87
+
88
+ deleteConfiguration: _base
89
+ .meta({
90
+ userType: "authenticated",
91
+ permissions: [permissions.healthCheckManage.id],
92
+ })
93
+ .input(z.string())
94
+ .output(z.void()),
95
+
96
+ // ==========================================================================
97
+ // SYSTEM ASSOCIATION (userType: "authenticated")
98
+ // ==========================================================================
99
+
100
+ getSystemConfigurations: _base
101
+ .meta({
102
+ userType: "authenticated",
103
+ permissions: [permissions.healthCheckRead.id],
104
+ })
105
+ .input(z.string())
106
+ .output(z.array(HealthCheckConfigurationSchema)),
107
+
108
+ /**
109
+ * Get system associations with their threshold configurations.
110
+ * Returns full association data including enabled state and thresholds.
111
+ */
112
+ getSystemAssociations: _base
113
+ .meta({
114
+ userType: "authenticated",
115
+ permissions: [permissions.healthCheckRead.id],
116
+ })
117
+ .input(z.object({ systemId: z.string() }))
118
+ .output(
119
+ z.array(
120
+ z.object({
121
+ configurationId: z.string(),
122
+ configurationName: z.string(),
123
+ enabled: z.boolean(),
124
+ stateThresholds: StateThresholdsSchema.optional(),
125
+ })
126
+ )
127
+ ),
128
+
129
+ associateSystem: _base
130
+ .meta({
131
+ userType: "authenticated",
132
+ permissions: [permissions.healthCheckManage.id],
133
+ })
134
+ .input(
135
+ z.object({
136
+ systemId: z.string(),
137
+ body: AssociateHealthCheckSchema,
138
+ })
139
+ )
140
+ .output(z.void()),
141
+
142
+ disassociateSystem: _base
143
+ .meta({
144
+ userType: "authenticated",
145
+ permissions: [permissions.healthCheckManage.id],
146
+ })
147
+ .input(
148
+ z.object({
149
+ systemId: z.string(),
150
+ configId: z.string(),
151
+ })
152
+ )
153
+ .output(z.void()),
154
+
155
+ // ==========================================================================
156
+ // RETENTION CONFIGURATION (userType: "authenticated" with manage permission)
157
+ // ==========================================================================
158
+
159
+ getRetentionConfig: _base
160
+ .meta({
161
+ userType: "authenticated",
162
+ permissions: [permissions.healthCheckRead.id],
163
+ })
164
+ .input(
165
+ z.object({
166
+ systemId: z.string(),
167
+ configurationId: z.string(),
168
+ })
169
+ )
170
+ .output(
171
+ z.object({
172
+ retentionConfig: RetentionConfigSchema.nullable(),
173
+ })
174
+ ),
175
+
176
+ updateRetentionConfig: _base
177
+ .meta({
178
+ userType: "authenticated",
179
+ permissions: [permissions.healthCheckManage.id],
180
+ })
181
+ .input(
182
+ z.object({
183
+ systemId: z.string(),
184
+ configurationId: z.string(),
185
+ retentionConfig: RetentionConfigSchema.nullable(),
186
+ })
187
+ )
188
+ .output(z.void()),
189
+
190
+ // ==========================================================================
191
+ // HISTORY & STATUS (userType: "user" with read permission)
192
+ // ==========================================================================
193
+
194
+ getHistory: _base
195
+ .meta({
196
+ userType: "public",
197
+ permissions: [permissions.healthCheckStatusRead.id],
198
+ })
199
+ .input(
200
+ z.object({
201
+ systemId: z.string().optional(),
202
+ configurationId: z.string().optional(),
203
+ startDate: z.date().optional(),
204
+ endDate: z.date().optional(),
205
+ limit: z.number().optional().default(10),
206
+ offset: z.number().optional().default(0),
207
+ })
208
+ )
209
+ .output(
210
+ z.object({
211
+ runs: z.array(HealthCheckRunPublicSchema),
212
+ total: z.number(),
213
+ })
214
+ ),
215
+
216
+ /**
217
+ * Get detailed health check run history with full result data.
218
+ * Requires permission to view detailed run data including metadata.
219
+ */
220
+ getDetailedHistory: _base
221
+ .meta({
222
+ userType: "authenticated",
223
+ permissions: [permissions.healthCheckDetailsRead.id],
224
+ })
225
+ .input(
226
+ z.object({
227
+ systemId: z.string().optional(),
228
+ configurationId: z.string().optional(),
229
+ startDate: z.date().optional(),
230
+ endDate: z.date().optional(),
231
+ limit: z.number().optional().default(10),
232
+ offset: z.number().optional().default(0),
233
+ })
234
+ )
235
+ .output(
236
+ z.object({
237
+ runs: z.array(HealthCheckRunSchema),
238
+ total: z.number(),
239
+ })
240
+ ),
241
+
242
+ /**
243
+ * Get aggregated health check history for long-term analysis.
244
+ * Returns pre-computed buckets with core metrics only (no strategy-specific data).
245
+ * For strategy-specific aggregated results, use getDetailedAggregatedHistory.
246
+ */
247
+ getAggregatedHistory: _base
248
+ .meta({
249
+ userType: "public",
250
+ permissions: [permissions.healthCheckStatusRead.id],
251
+ })
252
+ .input(
253
+ z.object({
254
+ systemId: z.string(),
255
+ configurationId: z.string(),
256
+ startDate: z.date(),
257
+ endDate: z.date(),
258
+ bucketSize: z.enum(["hourly", "daily", "auto"]),
259
+ })
260
+ )
261
+ .output(
262
+ z.object({
263
+ buckets: z.array(AggregatedBucketBaseSchema),
264
+ })
265
+ ),
266
+
267
+ /**
268
+ * Get detailed aggregated health check history including strategy-specific data.
269
+ * Returns buckets with core metrics AND aggregatedResult from strategy.
270
+ * Requires healthCheckDetailsRead permission.
271
+ */
272
+ getDetailedAggregatedHistory: _base
273
+ .meta({
274
+ userType: "public",
275
+ permissions: [permissions.healthCheckDetailsRead.id],
276
+ })
277
+ .input(
278
+ z.object({
279
+ systemId: z.string(),
280
+ configurationId: z.string(),
281
+ startDate: z.date(),
282
+ endDate: z.date(),
283
+ bucketSize: z.enum(["hourly", "daily", "auto"]),
284
+ })
285
+ )
286
+ .output(
287
+ z.object({
288
+ buckets: z.array(AggregatedBucketSchema),
289
+ })
290
+ ),
291
+
292
+ /**
293
+ * Get evaluateted health status for a system based on configured thresholds.
294
+ * Aggregates all health check statuses for the system.
295
+ */
296
+ getSystemHealthStatus: _base
297
+ .meta({
298
+ userType: "public",
299
+ permissions: [permissions.healthCheckStatusRead.id],
300
+ })
301
+ .input(z.object({ systemId: z.string() }))
302
+ .output(SystemHealthStatusResponseSchema),
303
+
304
+ /**
305
+ * Get comprehensive health overview for a system.
306
+ * Returns all health checks with their last 25 runs for sparkline visualization.
307
+ */
308
+ getSystemHealthOverview: _base
309
+ .meta({
310
+ userType: "public",
311
+ permissions: [permissions.healthCheckStatusRead.id],
312
+ })
313
+ .input(z.object({ systemId: z.string() }))
314
+ .output(
315
+ z.object({
316
+ systemId: z.string(),
317
+ checks: z.array(
318
+ z.object({
319
+ configurationId: z.string(),
320
+ configurationName: z.string(),
321
+ strategyId: z.string(),
322
+ intervalSeconds: z.number(),
323
+ enabled: z.boolean(),
324
+ status: HealthCheckStatusSchema,
325
+ stateThresholds: StateThresholdsSchema.optional(),
326
+ recentRuns: z.array(
327
+ z.object({
328
+ id: z.string(),
329
+ status: HealthCheckStatusSchema,
330
+ timestamp: z.date(),
331
+ })
332
+ ),
333
+ })
334
+ ),
335
+ })
336
+ ),
337
+ };
338
+
339
+ // Export contract type
340
+ export type HealthCheckContract = typeof healthCheckContract;
341
+
342
+ // Export client definition for type-safe forPlugin usage
343
+ // Use: const client = rpcApi.forPlugin(HealthCheckApi);
344
+ export const HealthCheckApi = createClientDefinition(
345
+ healthCheckContract,
346
+ pluginMetadata
347
+ );
package/src/schemas.ts ADDED
@@ -0,0 +1,235 @@
1
+ import { z } from "zod";
2
+
3
+ // --- API Request/Response Schemas (Zod) ---
4
+
5
+ export const HealthCheckStrategyDtoSchema = z.object({
6
+ id: z.string(),
7
+ displayName: z.string(),
8
+ description: z.string().optional(),
9
+ configSchema: z.record(z.string(), z.unknown()),
10
+ /** JSON Schema for per-run result metadata (with chart annotations) */
11
+ resultSchema: z.record(z.string(), z.unknown()).optional(),
12
+ /** JSON Schema for aggregated result metadata (with chart annotations) */
13
+ aggregatedResultSchema: z.record(z.string(), z.unknown()).optional(),
14
+ });
15
+
16
+ export const HealthCheckConfigurationSchema = z.object({
17
+ id: z.string(),
18
+ name: z.string(),
19
+ strategyId: z.string(),
20
+ config: z.record(z.string(), z.unknown()),
21
+ intervalSeconds: z.number(),
22
+ createdAt: z.date(),
23
+ updatedAt: z.date(),
24
+ });
25
+
26
+ export const CreateHealthCheckConfigurationSchema = z.object({
27
+ name: z.string().min(1),
28
+ strategyId: z.string().min(1),
29
+ config: z.record(z.string(), z.unknown()),
30
+ intervalSeconds: z.number().min(1),
31
+ });
32
+
33
+ export type CreateHealthCheckConfiguration = z.infer<
34
+ typeof CreateHealthCheckConfigurationSchema
35
+ >;
36
+
37
+ export const UpdateHealthCheckConfigurationSchema =
38
+ CreateHealthCheckConfigurationSchema.partial();
39
+
40
+ export type UpdateHealthCheckConfiguration = z.infer<
41
+ typeof UpdateHealthCheckConfigurationSchema
42
+ >;
43
+
44
+ /**
45
+ * Health check status enum - same as database enum.
46
+ */
47
+ export const HealthCheckStatusSchema = z.enum([
48
+ "healthy",
49
+ "unhealthy",
50
+ "degraded",
51
+ ]);
52
+
53
+ export type HealthCheckStatus = z.infer<typeof HealthCheckStatusSchema>;
54
+
55
+ // --- State Threshold Schemas ---
56
+
57
+ /**
58
+ * Consecutive mode: evaluates based on sequential identical results.
59
+ * Good for stable systems where transient failures are rare.
60
+ */
61
+ export const ConsecutiveThresholdsSchema = z.object({
62
+ mode: z.literal("consecutive"),
63
+ /** Minimum consecutive successes to transition to healthy */
64
+ healthy: z.object({
65
+ minSuccessCount: z.number().int().min(1).default(1),
66
+ }),
67
+ /** Minimum consecutive failures to transition to degraded */
68
+ degraded: z.object({
69
+ minFailureCount: z.number().int().min(1).default(2),
70
+ }),
71
+ /** Minimum consecutive failures to transition to unhealthy */
72
+ unhealthy: z.object({
73
+ minFailureCount: z.number().int().min(1).default(5),
74
+ }),
75
+ });
76
+
77
+ export type ConsecutiveThresholds = z.infer<typeof ConsecutiveThresholdsSchema>;
78
+
79
+ /**
80
+ * Window mode: evaluates based on failure count within a sliding window.
81
+ * Better for flickering systems where failures are intermittent.
82
+ */
83
+ export const WindowThresholdsSchema = z.object({
84
+ mode: z.literal("window"),
85
+ /** Number of recent runs to evaluate */
86
+ windowSize: z.number().int().min(3).max(100).default(10),
87
+ /** Minimum failures in window to transition to degraded */
88
+ degraded: z.object({
89
+ minFailureCount: z.number().int().min(1).default(3),
90
+ }),
91
+ /** Minimum failures in window to transition to unhealthy */
92
+ unhealthy: z.object({
93
+ minFailureCount: z.number().int().min(1).default(7),
94
+ }),
95
+ });
96
+
97
+ export type WindowThresholds = z.infer<typeof WindowThresholdsSchema>;
98
+
99
+ /**
100
+ * Discriminated union of threshold modes
101
+ */
102
+ export const StateThresholdsSchema = z.discriminatedUnion("mode", [
103
+ ConsecutiveThresholdsSchema,
104
+ WindowThresholdsSchema,
105
+ ]);
106
+
107
+ export type StateThresholds = z.infer<typeof StateThresholdsSchema>;
108
+
109
+ /**
110
+ * Default thresholds for backward compatibility
111
+ */
112
+ export const DEFAULT_STATE_THRESHOLDS: StateThresholds = {
113
+ mode: "consecutive",
114
+ healthy: { minSuccessCount: 1 },
115
+ degraded: { minFailureCount: 2 },
116
+ unhealthy: { minFailureCount: 5 },
117
+ };
118
+
119
+ export const AssociateHealthCheckSchema = z.object({
120
+ configurationId: z.string().uuid(),
121
+ enabled: z.boolean().default(true),
122
+ stateThresholds: StateThresholdsSchema.optional(),
123
+ });
124
+
125
+ export type AssociateHealthCheck = z.infer<typeof AssociateHealthCheckSchema>;
126
+
127
+ export const GetHealthCheckHistoryQuerySchema = z.object({
128
+ systemId: z.string().uuid().optional(),
129
+ configurationId: z.string().uuid().optional(),
130
+ limit: z.number().optional(),
131
+ });
132
+
133
+ export const HealthCheckRunSchema = z.object({
134
+ id: z.string(),
135
+ configurationId: z.string(),
136
+ systemId: z.string(),
137
+ status: HealthCheckStatusSchema,
138
+ result: z.record(z.string(), z.unknown()),
139
+ timestamp: z.date(),
140
+ latencyMs: z.number().optional(),
141
+ });
142
+
143
+ export type HealthCheckRun = z.infer<typeof HealthCheckRunSchema>;
144
+
145
+ /**
146
+ * Schema for the result object stored in HealthCheckRun.result.
147
+ * Mirrors the HealthCheckResult type from backend-api but is available to frontend.
148
+ *
149
+ * Structure:
150
+ * - status: The health status from the strategy execution
151
+ * - latencyMs: Execution time in milliseconds
152
+ * - message: Human-readable status message
153
+ * - metadata: Strategy-specific fields (e.g., statusCode, contentType for HTTP)
154
+ */
155
+ export const StoredHealthCheckResultSchema = z.object({
156
+ status: HealthCheckStatusSchema,
157
+ latencyMs: z.number().optional(),
158
+ message: z.string().optional(),
159
+ metadata: z.record(z.string(), z.unknown()).optional(),
160
+ });
161
+
162
+ export type StoredHealthCheckResult = z.infer<
163
+ typeof StoredHealthCheckResultSchema
164
+ >;
165
+
166
+ /**
167
+ * Public schema for health check runs without sensitive result data.
168
+ * Used by public endpoints accessible to anonymous/authenticated users.
169
+ */
170
+ export const HealthCheckRunPublicSchema = z.object({
171
+ id: z.string(),
172
+ configurationId: z.string(),
173
+ systemId: z.string(),
174
+ status: HealthCheckStatusSchema,
175
+ timestamp: z.date(),
176
+ latencyMs: z.number().optional(),
177
+ });
178
+
179
+ export type HealthCheckRunPublic = z.infer<typeof HealthCheckRunPublicSchema>;
180
+
181
+ // --- Retention Configuration ---
182
+
183
+ /**
184
+ * Retention configuration for health check data.
185
+ * Defines how long raw runs and aggregates are kept.
186
+ */
187
+ export const RetentionConfigSchema = z.object({
188
+ /** Days to keep raw run data before aggregating (default: 7) */
189
+ rawRetentionDays: z.number().int().min(1).max(30).default(7),
190
+ /** Days to keep hourly aggregates before rolling to daily (default: 30) */
191
+ hourlyRetentionDays: z.number().int().min(7).max(90).default(30),
192
+ /** Days to keep daily aggregates before deleting (default: 365) */
193
+ dailyRetentionDays: z.number().int().min(30).max(1095).default(365),
194
+ });
195
+
196
+ export type RetentionConfig = z.infer<typeof RetentionConfigSchema>;
197
+
198
+ export const DEFAULT_RETENTION_CONFIG: RetentionConfig = {
199
+ rawRetentionDays: 7,
200
+ hourlyRetentionDays: 30,
201
+ dailyRetentionDays: 365,
202
+ };
203
+
204
+ // --- Aggregated Bucket Schema ---
205
+
206
+ /**
207
+ * Base schema for aggregated health check data buckets.
208
+ * Contains core metrics only (no strategy-specific data).
209
+ * Used by getAggregatedHistory endpoint (healthCheckStatusRead permission).
210
+ */
211
+ export const AggregatedBucketBaseSchema = z.object({
212
+ bucketStart: z.date(),
213
+ bucketSize: z.enum(["hourly", "daily"]),
214
+ runCount: z.number(),
215
+ healthyCount: z.number(),
216
+ degradedCount: z.number(),
217
+ unhealthyCount: z.number(),
218
+ successRate: z.number(),
219
+ avgLatencyMs: z.number().optional(),
220
+ minLatencyMs: z.number().optional(),
221
+ maxLatencyMs: z.number().optional(),
222
+ p95LatencyMs: z.number().optional(),
223
+ });
224
+
225
+ export type AggregatedBucketBase = z.infer<typeof AggregatedBucketBaseSchema>;
226
+
227
+ /**
228
+ * Extended schema with strategy-specific aggregated result.
229
+ * Used by getDetailedAggregatedHistory endpoint (healthCheckDetailsRead permission).
230
+ */
231
+ export const AggregatedBucketSchema = AggregatedBucketBaseSchema.extend({
232
+ aggregatedResult: z.record(z.string(), z.unknown()).optional(),
233
+ });
234
+
235
+ export type AggregatedBucket = z.infer<typeof AggregatedBucketSchema>;
@@ -0,0 +1,87 @@
1
+ import { z } from "zod";
2
+
3
+ // ============================================================================
4
+ // HEALTH RESULT REGISTRY - Typed metadata for chart annotations
5
+ // ============================================================================
6
+
7
+ /**
8
+ * Chart types for auto-generated health check visualizations.
9
+ *
10
+ * Numeric types:
11
+ * - line: Time series line chart for numeric metrics over time
12
+ * - bar: Bar chart for distributions (record of string to number)
13
+ * - counter: Simple count display with trend indicator
14
+ * - gauge: Percentage gauge for rates/percentages (0-100)
15
+ *
16
+ * Non-numeric types:
17
+ * - boolean: Boolean indicator (success/failure, connected/disconnected)
18
+ * - text: Text display for string values
19
+ * - status: Status badge for error/warning states
20
+ */
21
+ export type ChartType =
22
+ | "line"
23
+ | "bar"
24
+ | "counter"
25
+ | "gauge"
26
+ | "boolean"
27
+ | "text"
28
+ | "status";
29
+
30
+ /**
31
+ * Metadata type for health check result schemas.
32
+ * Provides autocompletion for `.meta()` calls on result fields.
33
+ */
34
+ export interface HealthResultMeta {
35
+ /** The type of chart to render for this field */
36
+ "x-chart-type"?: ChartType;
37
+ /** Human-readable label for the chart (defaults to field name) */
38
+ "x-chart-label"?: string;
39
+ /** Unit suffix for values (e.g., 'ms', '%', 'req/s') */
40
+ "x-chart-unit"?: string;
41
+ }
42
+
43
+ /**
44
+ * Registry for health result schema metadata.
45
+ * Used by auto-chart components for visualization inference.
46
+ */
47
+ export const healthResultRegistry = z.registry<HealthResultMeta>();
48
+
49
+ // ============================================================================
50
+ // TYPED HEALTH RESULT FACTORIES
51
+ // ============================================================================
52
+
53
+ /**
54
+ * Create a health result string field with typed chart metadata.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * import { healthResultString } from "@checkstack/healthcheck-common";
59
+ *
60
+ * const resultSchema = z.object({
61
+ * role: healthResultString({ "x-chart-type": "text", "x-chart-label": "Role" }),
62
+ * });
63
+ * ```
64
+ */
65
+ export function healthResultString(meta: HealthResultMeta) {
66
+ const schema = z.string();
67
+ schema.register(healthResultRegistry, meta);
68
+ return schema;
69
+ }
70
+
71
+ /**
72
+ * Create a health result number field with typed chart metadata.
73
+ */
74
+ export function healthResultNumber(meta: HealthResultMeta) {
75
+ const schema = z.number();
76
+ schema.register(healthResultRegistry, meta);
77
+ return schema;
78
+ }
79
+
80
+ /**
81
+ * Create a health result boolean field with typed chart metadata.
82
+ */
83
+ export function healthResultBoolean(meta: HealthResultMeta) {
84
+ const schema = z.boolean();
85
+ schema.register(healthResultRegistry, meta);
86
+ return schema;
87
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "@checkstack/tsconfig/common.json",
3
+ "include": [
4
+ "src"
5
+ ]
6
+ }