@checkstack/healthcheck-backend 1.0.0 → 1.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 CHANGED
@@ -1,5 +1,59 @@
1
1
  # @checkstack/healthcheck-backend
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [302cd3f]
8
+ - @checkstack/backend-api@0.14.1
9
+ - @checkstack/cache-api@0.2.3
10
+ - @checkstack/catalog-backend@1.0.1
11
+ - @checkstack/command-backend@0.1.23
12
+ - @checkstack/gitops-backend@0.2.7
13
+ - @checkstack/integration-backend@0.1.23
14
+ - @checkstack/queue-api@0.2.17
15
+ - @checkstack/satellite-backend@0.2.20
16
+ - @checkstack/cache-utils@0.2.3
17
+ - @checkstack/catalog-common@2.0.0
18
+ - @checkstack/common@0.7.0
19
+ - @checkstack/gitops-common@0.2.1
20
+ - @checkstack/healthcheck-common@1.0.0
21
+ - @checkstack/incident-common@1.0.0
22
+ - @checkstack/maintenance-common@1.0.0
23
+ - @checkstack/notification-common@1.0.0
24
+ - @checkstack/signal-common@0.2.0
25
+
26
+ ## 1.0.1
27
+
28
+ ### Patch Changes
29
+
30
+ - 2a749d3: fix: run afterPluginsReady in topological order; merge daily rollups on conflict
31
+
32
+ Two resilience fixes for the dependency chain:
33
+
34
+ 1. **Plugin loader**: Phase 3 (`afterPluginsReady`) now iterates plugins
35
+ in the same topologically-sorted order as Phase 2 (`init`). Previously
36
+ it iterated `pendingInits` in registration order, which raced
37
+ subscription-spec dependencies — catalog's afterPluginsReady registers
38
+ `catalog.system` and `catalog.group` notification targets, and emitting
39
+ plugins (incident, maintenance, …) call `registerSubscriptionSpec`
40
+ against those targets in their own afterPluginsReady. With registration
41
+ order, an emitter could run before catalog and hit
42
+ `Target type catalog.group is not registered`. Sorted order encodes
43
+ the dependency via `spec.target.ownerPlugin`, so the emitter now
44
+ always runs after the target owner.
45
+
46
+ 2. **Healthcheck retention job**: the daily rollup now upserts
47
+ `health_check_aggregates` with `ON CONFLICT DO UPDATE` instead of a
48
+ plain insert. Previously, late-arriving hourly aggregates (e.g. from
49
+ a satellite that was offline when the prior rollup ran) would crash
50
+ the rollup with a unique-constraint violation on
51
+ `(configuration_id, system_id, bucket_start, bucket_size, source_id)`.
52
+ The merge sums counts and folds min/max/p95 into the existing daily
53
+ row.
54
+
55
+ - @checkstack/satellite-backend@0.2.19
56
+
3
57
  ## 1.0.0
4
58
 
5
59
  ### Major Changes
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-backend",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "checkstack": {
7
7
  "type": "backend"
8
8
  },
9
9
  "scripts": {
10
- "typecheck": "tsc --noEmit",
10
+ "typecheck": "tsgo -b",
11
11
  "generate": "drizzle-kit generate",
12
12
  "lint": "bun run lint:code",
13
13
  "lint:code": "eslint . --max-warnings 0"
14
14
  },
15
15
  "dependencies": {
16
- "@checkstack/backend-api": "0.13.1",
17
- "@checkstack/cache-api": "0.2.1",
18
- "@checkstack/cache-utils": "0.2.1",
19
- "@checkstack/catalog-backend": "0.7.1",
20
- "@checkstack/catalog-common": "1.5.3",
21
- "@checkstack/command-backend": "0.1.21",
16
+ "@checkstack/backend-api": "0.14.0",
17
+ "@checkstack/cache-api": "0.2.2",
18
+ "@checkstack/cache-utils": "0.2.2",
19
+ "@checkstack/catalog-backend": "1.0.0",
20
+ "@checkstack/catalog-common": "2.0.0",
21
+ "@checkstack/command-backend": "0.1.22",
22
22
  "@checkstack/common": "0.7.0",
23
- "@checkstack/gitops-backend": "0.2.5",
23
+ "@checkstack/gitops-backend": "0.2.6",
24
24
  "@checkstack/gitops-common": "0.2.1",
25
- "@checkstack/healthcheck-common": "0.13.0",
26
- "@checkstack/incident-common": "0.5.0",
27
- "@checkstack/integration-backend": "0.1.21",
28
- "@checkstack/maintenance-common": "0.5.0",
29
- "@checkstack/notification-common": "0.3.0",
30
- "@checkstack/queue-api": "0.2.15",
31
- "@checkstack/satellite-backend": "0.2.17",
25
+ "@checkstack/healthcheck-common": "1.0.0",
26
+ "@checkstack/incident-common": "1.0.0",
27
+ "@checkstack/integration-backend": "0.1.22",
28
+ "@checkstack/maintenance-common": "1.0.0",
29
+ "@checkstack/notification-common": "1.0.0",
30
+ "@checkstack/queue-api": "0.2.16",
31
+ "@checkstack/satellite-backend": "0.2.19",
32
32
  "@checkstack/signal-common": "0.2.0",
33
33
  "@hono/zod-validator": "^0.7.6",
34
34
  "drizzle-orm": "^0.45.0",
@@ -40,7 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@checkstack/drizzle-helper": "0.0.4",
42
42
  "@checkstack/scripts": "0.1.2",
43
- "@checkstack/test-utils-backend": "0.1.21",
43
+ "@checkstack/test-utils-backend": "0.1.22",
44
44
  "@checkstack/tsconfig": "0.0.5",
45
45
  "@types/bun": "^1.0.0",
46
46
  "@types/tdigest": "^0.1.5",
@@ -6,7 +6,7 @@ import {
6
6
  healthCheckAggregates,
7
7
  DEFAULT_RETENTION_CONFIG,
8
8
  } from "./schema";
9
- import { eq, and, lt } from "drizzle-orm";
9
+ import { eq, and, lt, sql } from "drizzle-orm";
10
10
  import type { QueueManager } from "@checkstack/queue-api";
11
11
 
12
12
  type Db = SafeDatabase<typeof schema>;
@@ -228,23 +228,59 @@ async function rollupHourlyAggregates(params: RollupParams) {
228
228
  const p95LatencyMs =
229
229
  p95Values.length > 0 ? Math.max(...p95Values) : undefined;
230
230
 
231
- // Insert daily aggregate
232
- await db.insert(healthCheckAggregates).values({
233
- configurationId,
234
- systemId,
235
- bucketStart: bucket.bucketStart,
236
- bucketSize: "daily",
237
- runCount,
238
- healthyCount,
239
- degradedCount,
240
- unhealthyCount,
241
- latencySumMs: latencySumMs > 0 ? latencySumMs : undefined,
242
- avgLatencyMs,
243
- minLatencyMs,
244
- maxLatencyMs,
245
- p95LatencyMs,
246
- aggregatedResult: undefined, // Cannot combine result across hours
247
- });
231
+ // Upsert the daily aggregate. A row may already exist for this
232
+ // (configurationId, systemId, day, daily, sourceId=null) tuple if a
233
+ // prior rollup ran and then late-arriving hourly buckets (e.g. from
234
+ // a satellite that was offline) were rolled up afterwards. Merge in
235
+ // that case rather than crashing — sums add, min/max/p95 fold.
236
+ const newLatencySum = latencySumMs > 0 ? latencySumMs : undefined;
237
+ await db
238
+ .insert(healthCheckAggregates)
239
+ .values({
240
+ configurationId,
241
+ systemId,
242
+ bucketStart: bucket.bucketStart,
243
+ bucketSize: "daily",
244
+ runCount,
245
+ healthyCount,
246
+ degradedCount,
247
+ unhealthyCount,
248
+ latencySumMs: newLatencySum,
249
+ avgLatencyMs,
250
+ minLatencyMs,
251
+ maxLatencyMs,
252
+ p95LatencyMs,
253
+ aggregatedResult: undefined, // Cannot combine result across hours
254
+ })
255
+ .onConflictDoUpdate({
256
+ target: [
257
+ healthCheckAggregates.configurationId,
258
+ healthCheckAggregates.systemId,
259
+ healthCheckAggregates.bucketStart,
260
+ healthCheckAggregates.bucketSize,
261
+ healthCheckAggregates.sourceId,
262
+ ],
263
+ set: {
264
+ runCount: sql`${healthCheckAggregates.runCount} + ${runCount}`,
265
+ healthyCount: sql`${healthCheckAggregates.healthyCount} + ${healthyCount}`,
266
+ degradedCount: sql`${healthCheckAggregates.degradedCount} + ${degradedCount}`,
267
+ unhealthyCount: sql`${healthCheckAggregates.unhealthyCount} + ${unhealthyCount}`,
268
+ latencySumMs: sql`COALESCE(${healthCheckAggregates.latencySumMs}, 0) + ${newLatencySum ?? 0}`,
269
+ avgLatencyMs: sql`CASE WHEN (${healthCheckAggregates.runCount} + ${runCount}) > 0 THEN (COALESCE(${healthCheckAggregates.latencySumMs}, 0) + ${newLatencySum ?? 0}) / (${healthCheckAggregates.runCount} + ${runCount}) ELSE ${healthCheckAggregates.avgLatencyMs} END`,
270
+ minLatencyMs:
271
+ minLatencyMs === undefined
272
+ ? sql`${healthCheckAggregates.minLatencyMs}`
273
+ : sql`LEAST(COALESCE(${healthCheckAggregates.minLatencyMs}, ${minLatencyMs}), ${minLatencyMs})`,
274
+ maxLatencyMs:
275
+ maxLatencyMs === undefined
276
+ ? sql`${healthCheckAggregates.maxLatencyMs}`
277
+ : sql`GREATEST(COALESCE(${healthCheckAggregates.maxLatencyMs}, ${maxLatencyMs}), ${maxLatencyMs})`,
278
+ p95LatencyMs:
279
+ p95LatencyMs === undefined
280
+ ? sql`${healthCheckAggregates.p95LatencyMs}`
281
+ : sql`GREATEST(COALESCE(${healthCheckAggregates.p95LatencyMs}, ${p95LatencyMs}), ${p95LatencyMs})`,
282
+ },
283
+ });
248
284
 
249
285
  // Delete processed hourly aggregates
250
286
  for (const hourly of bucket.aggregates) {
package/tsconfig.json CHANGED
@@ -2,5 +2,64 @@
2
2
  "extends": "@checkstack/tsconfig/backend.json",
3
3
  "include": [
4
4
  "src"
5
+ ],
6
+ "references": [
7
+ {
8
+ "path": "../backend-api"
9
+ },
10
+ {
11
+ "path": "../cache-api"
12
+ },
13
+ {
14
+ "path": "../cache-utils"
15
+ },
16
+ {
17
+ "path": "../catalog-backend"
18
+ },
19
+ {
20
+ "path": "../catalog-common"
21
+ },
22
+ {
23
+ "path": "../command-backend"
24
+ },
25
+ {
26
+ "path": "../common"
27
+ },
28
+ {
29
+ "path": "../drizzle-helper"
30
+ },
31
+ {
32
+ "path": "../gitops-backend"
33
+ },
34
+ {
35
+ "path": "../gitops-common"
36
+ },
37
+ {
38
+ "path": "../healthcheck-common"
39
+ },
40
+ {
41
+ "path": "../incident-common"
42
+ },
43
+ {
44
+ "path": "../integration-backend"
45
+ },
46
+ {
47
+ "path": "../maintenance-common"
48
+ },
49
+ {
50
+ "path": "../notification-common"
51
+ },
52
+ {
53
+ "path": "../queue-api"
54
+ },
55
+ {
56
+ "path": "../satellite-backend"
57
+ },
58
+ {
59
+ "path": "../signal-common"
60
+ },
61
+ {
62
+ "path": "../test-utils-backend"
63
+ }
5
64
  ]
6
- }
65
+ }