@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
|
|
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.
|
|
12
|
-
"@checkstack/signal-common": "0.
|
|
13
|
-
"@checkstack/backend-api": "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
|
|
10
|
-
"test.broadcast",
|
|
11
|
-
z.object({ message: z.string() })
|
|
12
|
-
);
|
|
9
|
+
const testPluginMetadata = { pluginId: "test" };
|
|
13
10
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
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: {
|
|
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
|
});
|
package/src/websocket-handler.ts
CHANGED
|
@@ -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
|
};
|