@checkstack/signal-backend 0.1.20 → 0.2.1

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,51 @@
1
1
  # @checkstack/signal-backend
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [32d52c6]
8
+ - Updated dependencies [32d52c6]
9
+ - Updated dependencies [32d52c6]
10
+ - @checkstack/backend-api@0.14.0
11
+
12
+ ## 0.2.0
13
+
14
+ ### Minor Changes
15
+
16
+ - 208ad71: Centralize realtime cache invalidation: signals now carry their owning `pluginId` end-to-end, and a single `SignalAutoInvalidator` mounted near the React Query client invalidates `[[pluginId]]` for every incoming signal automatically.
17
+
18
+ **Breaking change to `createSignal`** (`@checkstack/signal-common`): the factory now takes a single object argument with `pluginMetadata`, `event`, and `payloadSchema`. The signal id is constructed as `${pluginMetadata.pluginId}.${event}` and the resulting `Signal` carries a `pluginId` field. The `SignalMessage` wire envelope and `ServerToClientMessage` `signal` variant gained a `pluginId` field so the frontend can route invalidations without parsing the id.
19
+
20
+ ```ts
21
+ // Before
22
+ export const ANOMALY_STATE_CHANGED = createSignal(
23
+ "anomaly.state_changed",
24
+ z.object({ ... }),
25
+ );
26
+
27
+ // After
28
+ export const ANOMALY_STATE_CHANGED = createSignal({
29
+ pluginMetadata,
30
+ event: "state_changed",
31
+ payloadSchema: z.object({ ... }),
32
+ });
33
+ ```
34
+
35
+ **New plugin field**: `FrontendPlugin.foreignSignals?: Signal<unknown>[]` lets a plugin opt its `[[pluginId]]` cache into invalidation when another plugin's signal fires (e.g. `dependency-frontend` declares `[SYSTEM_STATUS_CHANGED]` because dependency payloads embed system status). Same-plugin signals must NOT be listed — they are always auto-invalidated.
36
+
37
+ **Removed boilerplate**: per-component `useSignal(X, () => refetch())` and `useSignal(X, () => queryClient.invalidateQueries(...))` calls have been removed across `incident-frontend`, `maintenance-frontend`, `healthcheck-frontend`, `slo-frontend`, `dependency-frontend`, `satellite-frontend`, `announcement-frontend`, `notification-frontend`, and `dashboard-frontend`. The `NotificationBell` unread count is now derived directly from the `getUnreadCount` query (auto-invalidated) instead of a local state mirror.
38
+
39
+ **User-visible bug fix**: the system detail page anomaly widget (`SystemAnomalyWidget`) now updates in real-time when anomalies change, with no per-widget signal subscription required. The dashboard status page also stays fresh on `ANOMALY_STATE_CHANGED`, `ANOMALY_BASELINE_UPDATED`, and `ANOMALY_TREND_DETECTED`.
40
+
41
+ UI-state consumers that legitimately need a `useSignal` (the dashboard activity terminal, the queue lag alert, and the rolling-preset date refresh in `useHealthCheckData`) keep their handlers; the auto-invalidator runs alongside them.
42
+
43
+ ### Patch Changes
44
+
45
+ - Updated dependencies [208ad71]
46
+ - @checkstack/signal-common@0.2.0
47
+ - @checkstack/backend-api@0.13.1
48
+
3
49
  ## 0.1.20
4
50
 
5
51
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/signal-backend",
3
- "version": "0.1.20",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -8,9 +8,9 @@
8
8
  }
9
9
  },
10
10
  "dependencies": {
11
- "@checkstack/common": "0.6.5",
12
- "@checkstack/signal-common": "0.1.9",
13
- "@checkstack/backend-api": "0.12.0"
11
+ "@checkstack/common": "0.7.0",
12
+ "@checkstack/signal-common": "0.2.0",
13
+ "@checkstack/backend-api": "0.13.1"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/bun": "latest",
@@ -6,15 +6,19 @@ import { z } from "zod";
6
6
  import type { EventBus, Logger } from "@checkstack/backend-api";
7
7
 
8
8
  // Test signals
9
- const TEST_BROADCAST_SIGNAL = createSignal(
10
- "test.broadcast",
11
- z.object({ message: z.string() })
12
- );
9
+ const testPluginMetadata = { pluginId: "test" };
13
10
 
14
- const TEST_USER_SIGNAL = createSignal(
15
- "test.user",
16
- z.object({ notification: z.string(), count: z.number() })
17
- );
11
+ const TEST_BROADCAST_SIGNAL = createSignal({
12
+ pluginMetadata: testPluginMetadata,
13
+ event: "broadcast",
14
+ payloadSchema: z.object({ message: z.string() }),
15
+ });
16
+
17
+ const TEST_USER_SIGNAL = createSignal({
18
+ pluginMetadata: testPluginMetadata,
19
+ event: "user",
20
+ payloadSchema: z.object({ notification: z.string(), count: z.number() }),
21
+ });
18
22
 
19
23
  describe("SignalServiceImpl", () => {
20
24
  let signalService: SignalServiceImpl;
@@ -55,10 +59,12 @@ describe("SignalServiceImpl", () => {
55
59
 
56
60
  const message = emittedEvents[0].payload as {
57
61
  signalId: string;
62
+ pluginId: string;
58
63
  payload: typeof payload;
59
64
  timestamp: string;
60
65
  };
61
66
  expect(message.signalId).toBe("test.broadcast");
67
+ expect(message.pluginId).toBe("test");
62
68
  expect(message.payload).toEqual(payload);
63
69
  expect(typeof message.timestamp).toBe("string");
64
70
  });
@@ -96,10 +102,15 @@ describe("SignalServiceImpl", () => {
96
102
 
97
103
  const emitted = emittedEvents[0].payload as {
98
104
  userId: string;
99
- message: { signalId: string; payload: typeof payload };
105
+ message: {
106
+ signalId: string;
107
+ pluginId: string;
108
+ payload: typeof payload;
109
+ };
100
110
  };
101
111
  expect(emitted.userId).toBe(userId);
102
112
  expect(emitted.message.signalId).toBe("test.user");
113
+ expect(emitted.message.pluginId).toBe("test");
103
114
  expect(emitted.message.payload).toEqual(payload);
104
115
  });
105
116
 
@@ -40,6 +40,7 @@ export class SignalServiceImpl implements SignalService {
40
40
  async broadcast<T>(signal: Signal<T>, payload: T): Promise<void> {
41
41
  const message: SignalMessage<T> = {
42
42
  signalId: signal.id,
43
+ pluginId: signal.pluginId,
43
44
  payload,
44
45
  timestamp: new Date().toISOString(),
45
46
  };
@@ -57,6 +58,7 @@ export class SignalServiceImpl implements SignalService {
57
58
  ): Promise<void> {
58
59
  const message: SignalMessage<T> = {
59
60
  signalId: signal.id,
61
+ pluginId: signal.pluginId,
60
62
  payload,
61
63
  timestamp: new Date().toISOString(),
62
64
  };
@@ -296,6 +296,7 @@ describe("createWebSocketHandler", () => {
296
296
 
297
297
  await broadcastSubscription!.handler({
298
298
  signalId: "test.signal",
299
+ pluginId: "test",
299
300
  payload: { data: "test" },
300
301
  timestamp: new Date().toISOString(),
301
302
  });
@@ -306,6 +307,7 @@ describe("createWebSocketHandler", () => {
306
307
  const parsed = JSON.parse(publishedMessage!);
307
308
  expect(parsed.type).toBe("signal");
308
309
  expect(parsed.signalId).toBe("test.signal");
310
+ expect(parsed.pluginId).toBe("test");
309
311
  });
310
312
 
311
313
  it("should publish user signals to user-specific channel", async () => {
@@ -336,6 +338,7 @@ describe("createWebSocketHandler", () => {
336
338
  userId: "target-user",
337
339
  message: {
338
340
  signalId: "notification.received",
341
+ pluginId: "notification",
339
342
  payload: { id: "n-1" },
340
343
  timestamp: new Date().toISOString(),
341
344
  },
@@ -347,6 +350,7 @@ describe("createWebSocketHandler", () => {
347
350
  const parsed = JSON.parse(publishedMessage!);
348
351
  expect(parsed.type).toBe("signal");
349
352
  expect(parsed.signalId).toBe("notification.received");
353
+ expect(parsed.pluginId).toBe("notification");
350
354
  });
351
355
  });
352
356
  });
@@ -86,6 +86,7 @@ export function createWebSocketHandler(
86
86
  const payload: ServerToClientMessage = {
87
87
  type: "signal",
88
88
  signalId: message.signalId,
89
+ pluginId: message.pluginId,
89
90
  payload: message.payload,
90
91
  timestamp: message.timestamp,
91
92
  };
@@ -103,6 +104,7 @@ export function createWebSocketHandler(
103
104
  const payload: ServerToClientMessage = {
104
105
  type: "signal",
105
106
  signalId: message.signalId,
107
+ pluginId: message.pluginId,
106
108
  payload: message.payload,
107
109
  timestamp: message.timestamp,
108
110
  };