@checkstack/healthcheck-backend 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 +181 -0
- package/drizzle/0000_stormy_slayback.sql +33 -0
- package/drizzle/0001_thin_shotgun.sql +1 -0
- package/drizzle/0002_closed_lucky_pierre.sql +19 -0
- package/drizzle/0003_powerful_rage.sql +1 -0
- package/drizzle/0004_short_ezekiel.sql +1 -0
- package/drizzle/0005_glossy_longshot.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +234 -0
- package/drizzle/meta/0001_snapshot.json +240 -0
- package/drizzle/meta/0002_snapshot.json +361 -0
- package/drizzle/meta/0003_snapshot.json +367 -0
- package/drizzle/meta/0004_snapshot.json +401 -0
- package/drizzle/meta/0005_snapshot.json +401 -0
- package/drizzle/meta/_journal.json +48 -0
- package/drizzle.config.ts +7 -0
- package/package.json +37 -0
- package/src/aggregation.test.ts +373 -0
- package/src/hooks.test.ts +16 -0
- package/src/hooks.ts +35 -0
- package/src/index.ts +195 -0
- package/src/queue-executor.test.ts +229 -0
- package/src/queue-executor.ts +569 -0
- package/src/retention-job.ts +404 -0
- package/src/router.test.ts +81 -0
- package/src/router.ts +157 -0
- package/src/schema.ts +153 -0
- package/src/service.ts +718 -0
- package/src/state-evaluator.test.ts +237 -0
- package/src/state-evaluator.ts +105 -0
- package/src/state-thresholds-migrations.ts +15 -0
- package/tsconfig.json +6 -0
package/src/schema.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pgTable,
|
|
3
|
+
pgEnum,
|
|
4
|
+
text,
|
|
5
|
+
jsonb,
|
|
6
|
+
integer,
|
|
7
|
+
boolean,
|
|
8
|
+
uuid,
|
|
9
|
+
timestamp,
|
|
10
|
+
primaryKey,
|
|
11
|
+
uniqueIndex,
|
|
12
|
+
} from "drizzle-orm/pg-core";
|
|
13
|
+
import type { StateThresholds } from "@checkstack/healthcheck-common";
|
|
14
|
+
import type { VersionedRecord } from "@checkstack/backend-api";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type alias for versioned state thresholds stored in the database.
|
|
18
|
+
* Uses VersionedRecord generic base for migration support.
|
|
19
|
+
*/
|
|
20
|
+
export type VersionedStateThresholds = VersionedRecord<StateThresholds>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Health check status enum for type-safe status values.
|
|
24
|
+
*/
|
|
25
|
+
export const healthCheckStatusEnum = pgEnum("health_check_status", [
|
|
26
|
+
"healthy",
|
|
27
|
+
"unhealthy",
|
|
28
|
+
"degraded",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export type HealthCheckStatus =
|
|
32
|
+
(typeof healthCheckStatusEnum.enumValues)[number];
|
|
33
|
+
|
|
34
|
+
export const healthCheckConfigurations = pgTable(
|
|
35
|
+
"health_check_configurations",
|
|
36
|
+
{
|
|
37
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
38
|
+
name: text("name").notNull(),
|
|
39
|
+
strategyId: text("strategy_id").notNull(),
|
|
40
|
+
config: jsonb("config").$type<Record<string, unknown>>().notNull(),
|
|
41
|
+
intervalSeconds: integer("interval_seconds").notNull(),
|
|
42
|
+
isTemplate: boolean("is_template").default(false),
|
|
43
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
44
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Retention configuration for health check data.
|
|
50
|
+
* Defines how long raw runs and aggregates are kept.
|
|
51
|
+
*/
|
|
52
|
+
export interface RetentionConfig {
|
|
53
|
+
/** Days to keep raw run data before aggregating (default: 7) */
|
|
54
|
+
rawRetentionDays: number;
|
|
55
|
+
/** Days to keep hourly aggregates before rolling to daily (default: 30) */
|
|
56
|
+
hourlyRetentionDays: number;
|
|
57
|
+
/** Days to keep daily aggregates before deleting (default: 365) */
|
|
58
|
+
dailyRetentionDays: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const DEFAULT_RETENTION_CONFIG: RetentionConfig = {
|
|
62
|
+
rawRetentionDays: 7,
|
|
63
|
+
hourlyRetentionDays: 30,
|
|
64
|
+
dailyRetentionDays: 365,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const systemHealthChecks = pgTable(
|
|
68
|
+
"system_health_checks",
|
|
69
|
+
{
|
|
70
|
+
systemId: text("system_id").notNull(),
|
|
71
|
+
configurationId: uuid("configuration_id")
|
|
72
|
+
.notNull()
|
|
73
|
+
.references(() => healthCheckConfigurations.id, { onDelete: "cascade" }),
|
|
74
|
+
enabled: boolean("enabled").default(true).notNull(),
|
|
75
|
+
/**
|
|
76
|
+
* State thresholds for evaluating health status.
|
|
77
|
+
* Versioned to allow schema evolution without migrations for existing rows.
|
|
78
|
+
*/
|
|
79
|
+
stateThresholds:
|
|
80
|
+
jsonb("state_thresholds").$type<VersionedStateThresholds>(),
|
|
81
|
+
/**
|
|
82
|
+
* Retention configuration for this health check assignment.
|
|
83
|
+
* Null means use default retention settings.
|
|
84
|
+
*/
|
|
85
|
+
retentionConfig: jsonb("retention_config").$type<RetentionConfig>(),
|
|
86
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
87
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
88
|
+
},
|
|
89
|
+
(t) => ({
|
|
90
|
+
pk: primaryKey({ columns: [t.systemId, t.configurationId] }),
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const healthCheckRuns = pgTable("health_check_runs", {
|
|
95
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
96
|
+
configurationId: uuid("configuration_id")
|
|
97
|
+
.notNull()
|
|
98
|
+
.references(() => healthCheckConfigurations.id, { onDelete: "cascade" }),
|
|
99
|
+
systemId: text("system_id").notNull(),
|
|
100
|
+
status: healthCheckStatusEnum("status").notNull(),
|
|
101
|
+
/** Execution duration in milliseconds */
|
|
102
|
+
latencyMs: integer("latency_ms"),
|
|
103
|
+
result: jsonb("result").$type<Record<string, unknown>>(),
|
|
104
|
+
timestamp: timestamp("timestamp").defaultNow().notNull(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Bucket size enum for aggregated data.
|
|
109
|
+
*/
|
|
110
|
+
export const bucketSizeEnum = pgEnum("bucket_size", ["hourly", "daily"]);
|
|
111
|
+
|
|
112
|
+
export type BucketSize = (typeof bucketSizeEnum.enumValues)[number];
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Aggregated health check data for long-term storage.
|
|
116
|
+
* Core metrics (counts, latency) are auto-calculated by platform.
|
|
117
|
+
* Strategy-specific result is aggregated via strategy.aggregateResult().
|
|
118
|
+
*/
|
|
119
|
+
export const healthCheckAggregates = pgTable(
|
|
120
|
+
"health_check_aggregates",
|
|
121
|
+
{
|
|
122
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
123
|
+
configurationId: uuid("configuration_id")
|
|
124
|
+
.notNull()
|
|
125
|
+
.references(() => healthCheckConfigurations.id, { onDelete: "cascade" }),
|
|
126
|
+
systemId: text("system_id").notNull(),
|
|
127
|
+
bucketStart: timestamp("bucket_start").notNull(),
|
|
128
|
+
bucketSize: bucketSizeEnum("bucket_size").notNull(),
|
|
129
|
+
|
|
130
|
+
// Core metrics - auto-calculated by platform
|
|
131
|
+
runCount: integer("run_count").notNull(),
|
|
132
|
+
healthyCount: integer("healthy_count").notNull(),
|
|
133
|
+
degradedCount: integer("degraded_count").notNull(),
|
|
134
|
+
unhealthyCount: integer("unhealthy_count").notNull(),
|
|
135
|
+
avgLatencyMs: integer("avg_latency_ms"),
|
|
136
|
+
minLatencyMs: integer("min_latency_ms"),
|
|
137
|
+
maxLatencyMs: integer("max_latency_ms"),
|
|
138
|
+
p95LatencyMs: integer("p95_latency_ms"),
|
|
139
|
+
|
|
140
|
+
// Strategy-specific aggregated result (versioned)
|
|
141
|
+
aggregatedResult:
|
|
142
|
+
jsonb("aggregated_result").$type<Record<string, unknown>>(),
|
|
143
|
+
},
|
|
144
|
+
(t) => ({
|
|
145
|
+
// Unique constraint for upsert operations
|
|
146
|
+
bucketUnique: uniqueIndex("health_check_aggregates_bucket_unique").on(
|
|
147
|
+
t.configurationId,
|
|
148
|
+
t.systemId,
|
|
149
|
+
t.bucketStart,
|
|
150
|
+
t.bucketSize
|
|
151
|
+
),
|
|
152
|
+
})
|
|
153
|
+
);
|