@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 +88 -0
- package/package.json +25 -0
- package/src/index.ts +59 -0
- package/src/permissions.ts +39 -0
- package/src/plugin-metadata.ts +9 -0
- package/src/routes.ts +10 -0
- package/src/rpc-contract.ts +347 -0
- package/src/schemas.ts +235 -0
- package/src/zod-health-result.ts +87 -0
- package/tsconfig.json +6 -0
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
|
+
}
|