@checkstack/notification-common 1.1.1 → 1.2.0
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 +96 -0
- package/package.json +1 -1
- package/src/routes.ts +6 -0
- package/src/rpc-contract.ts +34 -9
- package/src/schemas.ts +49 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# @checkstack/notification-common
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f23f3c9: Add per-channel notification delivery-attempt tracking
|
|
8
|
+
(Phase 8 of the v1 polishing plan). The external dispatch loop now
|
|
9
|
+
persists one row per `strategy.send(...)` call into a new
|
|
10
|
+
`notification_delivery_attempts` table - both successes and
|
|
11
|
+
failures - so silent delivery breakage (misconfigured webhooks, dead
|
|
12
|
+
channels) becomes queryable instead of buried in logs.
|
|
13
|
+
|
|
14
|
+
- `@checkstack/notification-backend` adds the
|
|
15
|
+
`notification_delivery_attempts` table, the matching Drizzle
|
|
16
|
+
migration, and a new `dispatchWithAttempt` helper that wraps every
|
|
17
|
+
external `strategy.send(...)` with duration measurement and
|
|
18
|
+
best-effort row persistence. The insert is intentionally
|
|
19
|
+
fire-and-forget: if writing the attempt row itself errors, the
|
|
20
|
+
dispatch loop logs and continues so visibility tracking can never
|
|
21
|
+
introduce a _new_ silent failure.
|
|
22
|
+
- `@checkstack/notification-common` exports a new
|
|
23
|
+
`DeliveryAttemptSchema` zod schema, the
|
|
24
|
+
`ListDeliveryAttemptsInputSchema =
|
|
25
|
+
PaginationInput.extend({ notificationId })` input, and a new
|
|
26
|
+
`getDeliveryAttempts` procedure on the contract. The procedure is
|
|
27
|
+
gated by the existing `notificationAccess.admin`
|
|
28
|
+
(`notification:manage`) access rule - no new permission was
|
|
29
|
+
introduced.
|
|
30
|
+
- `@checkstack/notification-frontend` adds a minimal admin-only
|
|
31
|
+
`DeliveryAttemptsPage` (route id `notification.deliveryAttempts`,
|
|
32
|
+
path `/notifications/delivery-attempts`) and an "Open inspector"
|
|
33
|
+
link from the Notification Settings page for users with
|
|
34
|
+
`notification:manage`. No client-side `isAdmin` gate - the FORBIDDEN
|
|
35
|
+
case is rendered via the standard error-state branch on the page,
|
|
36
|
+
enforced by the contract.
|
|
37
|
+
|
|
38
|
+
Visibility only: there is no retry mechanism in this phase. A
|
|
39
|
+
`failure` row is a final outcome an admin actions manually
|
|
40
|
+
(re-trigger the source event, fix the misconfigured channel).
|
|
41
|
+
Automated retries are deferred to v1.1.
|
|
42
|
+
|
|
43
|
+
Strategy errors thrown during `send(...)` are persisted via
|
|
44
|
+
`extractErrorMessage(error)` so secrets potentially embedded in raw
|
|
45
|
+
error objects (webhook URLs, OAuth tokens reachable from the strategy
|
|
46
|
+
send context) are not stored verbatim.
|
|
47
|
+
|
|
48
|
+
See the new
|
|
49
|
+
`docs/src/content/docs/backend/notification-delivery.md` page for the
|
|
50
|
+
full surface description.
|
|
51
|
+
|
|
52
|
+
- f23f3c9: Sweep every paginated `*-common` contract onto the canonical
|
|
53
|
+
`PaginationInput` / `PaginatedResult` from `@checkstack/common` and
|
|
54
|
+
remove the now-unused legacy exports.
|
|
55
|
+
|
|
56
|
+
**BREAKING CHANGE** - `@checkstack/common` drops the deprecated
|
|
57
|
+
`PaginationInputSchema`, `paginatedOutput`, and `PaginatedResponse`
|
|
58
|
+
symbols. Callers must consume `PaginationInput` (input) and
|
|
59
|
+
`PaginatedResult(itemSchema)` (output) instead. The canonical input is
|
|
60
|
+
`{ limit (1-100, default 20), offset (>= 0, default 0) }`; the
|
|
61
|
+
canonical output envelope is
|
|
62
|
+
`{ items, total, limit, offset }`.
|
|
63
|
+
|
|
64
|
+
**BREAKING CHANGE** - `@checkstack/notification-common` migrates
|
|
65
|
+
`getNotifications` off the legacy `PaginationInputSchema`
|
|
66
|
+
(`{ limit, offset, unreadOnly }` with output `{ notifications, total }`)
|
|
67
|
+
onto `ListNotificationsInputSchema =
|
|
68
|
+
PaginationInput.extend({ unreadOnly })` and
|
|
69
|
+
`PaginatedResult(NotificationSchema)`. The output key changes from
|
|
70
|
+
`notifications` to `items`, and `limit` / `offset` are now echoed on
|
|
71
|
+
the response. The `PaginationInput` type alias previously exported
|
|
72
|
+
from `notification-common` is removed - use `ListNotificationsInput`
|
|
73
|
+
or the canonical `PaginationInput` from `@checkstack/common`.
|
|
74
|
+
|
|
75
|
+
**BREAKING CHANGE** - `@checkstack/integration-common` migrates
|
|
76
|
+
`listSubscriptions` (inline `{ page, pageSize, ... }` -> output
|
|
77
|
+
`{ subscriptions, total }`) and `getDeliveryLogs` (via
|
|
78
|
+
`DeliveryLogQueryInputSchema` `{ subscriptionId?, eventType?, status?,
|
|
79
|
+
page, pageSize }` -> output `{ logs, total }`) onto the canonical
|
|
80
|
+
`PaginationInput.extend({...})` input and
|
|
81
|
+
`PaginatedResult(itemSchema)` output. External callers must switch
|
|
82
|
+
from `{ page, pageSize }` to `{ limit, offset }` and read response
|
|
83
|
+
items from `data.items` (no more `data.subscriptions` / `data.logs`).
|
|
84
|
+
|
|
85
|
+
The matching `*-backend` handlers were updated to consume the new
|
|
86
|
+
input shape (`offset` arithmetic in lieu of `(page - 1) * pageSize`)
|
|
87
|
+
and to echo `limit` / `offset` on the response. The `*-frontend` call
|
|
88
|
+
sites in `NotificationsPage`, `NotificationBell`, `IntegrationsPage`,
|
|
89
|
+
and `DeliveryLogsPage` were updated to send the new input shape and
|
|
90
|
+
read `data.items`.
|
|
91
|
+
|
|
92
|
+
### Patch Changes
|
|
93
|
+
|
|
94
|
+
- Updated dependencies [f23f3c9]
|
|
95
|
+
- Updated dependencies [f23f3c9]
|
|
96
|
+
- @checkstack/common@0.11.0
|
|
97
|
+
- @checkstack/signal-common@0.2.4
|
|
98
|
+
|
|
3
99
|
## 1.1.1
|
|
4
100
|
|
|
5
101
|
### Patch Changes
|
package/package.json
CHANGED
package/src/routes.ts
CHANGED
|
@@ -6,4 +6,10 @@ import { createRoutes } from "@checkstack/common";
|
|
|
6
6
|
export const notificationRoutes = createRoutes("notification", {
|
|
7
7
|
home: "/",
|
|
8
8
|
settings: "/settings",
|
|
9
|
+
/**
|
|
10
|
+
* Admin-only visibility surface for per-channel delivery attempts.
|
|
11
|
+
* Visibility, not a dashboard — see Phase 8 of the v1 polishing
|
|
12
|
+
* plan and `docs/.../backend/notification-delivery.md`.
|
|
13
|
+
*/
|
|
14
|
+
deliveryAttempts: "/delivery-attempts",
|
|
9
15
|
});
|
package/src/rpc-contract.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { notificationAccess } from "./access";
|
|
3
3
|
import { pluginMetadata } from "./plugin-metadata";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createClientDefinition,
|
|
6
|
+
PaginatedResult,
|
|
7
|
+
proc,
|
|
8
|
+
} from "@checkstack/common";
|
|
5
9
|
import {
|
|
6
10
|
NotificationSchema,
|
|
7
11
|
NotificationGroupSchema,
|
|
8
12
|
EnrichedSubscriptionSchema,
|
|
9
13
|
RetentionSettingsSchema,
|
|
10
|
-
|
|
14
|
+
ListNotificationsInputSchema,
|
|
11
15
|
NotificationSubjectSchema,
|
|
16
|
+
DeliveryAttemptSchema,
|
|
17
|
+
ListDeliveryAttemptsInputSchema,
|
|
12
18
|
} from "./schemas";
|
|
13
19
|
|
|
14
20
|
// Shared input fragments for the notify* procedures.
|
|
@@ -78,13 +84,8 @@ export const notificationContract = {
|
|
|
78
84
|
userType: "user",
|
|
79
85
|
access: [],
|
|
80
86
|
})
|
|
81
|
-
.input(
|
|
82
|
-
.output(
|
|
83
|
-
z.object({
|
|
84
|
-
notifications: z.array(NotificationSchema),
|
|
85
|
-
total: z.number(),
|
|
86
|
-
})
|
|
87
|
-
),
|
|
87
|
+
.input(ListNotificationsInputSchema)
|
|
88
|
+
.output(PaginatedResult(NotificationSchema)),
|
|
88
89
|
|
|
89
90
|
// Get unread count for badge
|
|
90
91
|
getUnreadCount: proc({
|
|
@@ -196,6 +197,30 @@ export const notificationContract = {
|
|
|
196
197
|
.input(RetentionSettingsSchema)
|
|
197
198
|
.output(z.void()),
|
|
198
199
|
|
|
200
|
+
/**
|
|
201
|
+
* List per-channel delivery attempts (paginated, newest first).
|
|
202
|
+
*
|
|
203
|
+
* Admin-only visibility surface for external-delivery outcomes. Each
|
|
204
|
+
* row corresponds to one `strategy.send(...)` call against an
|
|
205
|
+
* external notification channel; failures expose the silent-failure
|
|
206
|
+
* mode the dispatch loop swallowed pre-v1.
|
|
207
|
+
*
|
|
208
|
+
* When `notificationId` is supplied, results are scoped to that
|
|
209
|
+
* notification; otherwise the caller sees every recent attempt
|
|
210
|
+
* across the platform.
|
|
211
|
+
*
|
|
212
|
+
* Visibility-only — there is no retry mechanism in v1; a `failure`
|
|
213
|
+
* here is a final outcome that an admin actions manually (re-trigger
|
|
214
|
+
* the source event, fix the misconfigured channel, etc.).
|
|
215
|
+
*/
|
|
216
|
+
getDeliveryAttempts: proc({
|
|
217
|
+
operationType: "query",
|
|
218
|
+
userType: "user",
|
|
219
|
+
access: [notificationAccess.admin],
|
|
220
|
+
})
|
|
221
|
+
.input(ListDeliveryAttemptsInputSchema)
|
|
222
|
+
.output(PaginatedResult(DeliveryAttemptSchema)),
|
|
223
|
+
|
|
199
224
|
// ==========================================================================
|
|
200
225
|
// BACKEND-TO-BACKEND GROUP MANAGEMENT (userType: "service")
|
|
201
226
|
// ==========================================================================
|
package/src/schemas.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { PaginationInput as CanonicalPaginationInput } from "@checkstack/common";
|
|
2
3
|
|
|
3
4
|
// Notification importance levels
|
|
4
5
|
export const ImportanceSchema = z.enum(["info", "warning", "critical"]);
|
|
@@ -145,13 +146,14 @@ export type NotificationGroupInput = z.infer<
|
|
|
145
146
|
typeof NotificationGroupInputSchema
|
|
146
147
|
>;
|
|
147
148
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
// Notification list input — extends the canonical `PaginationInput` from
|
|
150
|
+
// `@checkstack/common` with the notification-specific `unreadOnly` filter.
|
|
151
|
+
// Compose with `.extend({...})` to add further domain filters; do NOT
|
|
152
|
+
// redefine the base `limit` / `offset` fields.
|
|
153
|
+
export const ListNotificationsInputSchema = CanonicalPaginationInput.extend({
|
|
152
154
|
unreadOnly: z.boolean().default(false),
|
|
153
155
|
});
|
|
154
|
-
export type
|
|
156
|
+
export type ListNotificationsInput = z.infer<typeof ListNotificationsInputSchema>;
|
|
155
157
|
|
|
156
158
|
// --- Notification Strategy Schemas ---
|
|
157
159
|
|
|
@@ -241,3 +243,45 @@ export const TransactionalResultSchema = z.object({
|
|
|
241
243
|
error: z.string().optional(),
|
|
242
244
|
});
|
|
243
245
|
export type TransactionalResult = z.infer<typeof TransactionalResultSchema>;
|
|
246
|
+
|
|
247
|
+
// --- Delivery attempt schemas ---
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Outcome of one external `strategy.send(...)` call. Visibility-only —
|
|
251
|
+
* `failure` here is a final, surfaced outcome (no retries in v1).
|
|
252
|
+
*/
|
|
253
|
+
export const DeliveryAttemptStatusSchema = z.enum(["success", "failure"]);
|
|
254
|
+
export type DeliveryAttemptStatus = z.infer<typeof DeliveryAttemptStatusSchema>;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* One row from `notification_delivery_attempts`. Mirrors the Drizzle
|
|
258
|
+
* table 1:1 — keep these in sync; the schema-drift CI check (Phase 10)
|
|
259
|
+
* will catch column-name skew once it lands.
|
|
260
|
+
*/
|
|
261
|
+
export const DeliveryAttemptSchema = z.object({
|
|
262
|
+
id: z.string().uuid(),
|
|
263
|
+
notificationId: z.string().uuid(),
|
|
264
|
+
/** Qualified strategy id, e.g. `notification-discord.send`. */
|
|
265
|
+
strategyQualifiedId: z.string(),
|
|
266
|
+
attemptedAt: z.coerce.date(),
|
|
267
|
+
status: DeliveryAttemptStatusSchema,
|
|
268
|
+
/** Sanitised via `extractErrorMessage` on the backend before persistence. */
|
|
269
|
+
errorMessage: z.string().nullable(),
|
|
270
|
+
/** Wall-clock duration of the `send()` call in milliseconds. */
|
|
271
|
+
durationMs: z.number().int().min(0),
|
|
272
|
+
});
|
|
273
|
+
export type DeliveryAttempt = z.infer<typeof DeliveryAttemptSchema>;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Input shape for `getDeliveryAttempts`. Extends the canonical
|
|
277
|
+
* `PaginationInput` with an optional `notificationId` filter — when
|
|
278
|
+
* supplied, results are scoped to that notification; otherwise the
|
|
279
|
+
* caller gets every attempt across the system (newest first), useful
|
|
280
|
+
* for admin "recent failures" dashboards.
|
|
281
|
+
*/
|
|
282
|
+
export const ListDeliveryAttemptsInputSchema = CanonicalPaginationInput.extend({
|
|
283
|
+
notificationId: z.string().uuid().optional(),
|
|
284
|
+
});
|
|
285
|
+
export type ListDeliveryAttemptsInput = z.infer<
|
|
286
|
+
typeof ListDeliveryAttemptsInputSchema
|
|
287
|
+
>;
|