@cat-factory/kernel 0.272.0 → 0.274.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/dist/domain/binary-store-registry.d.ts +98 -0
- package/dist/domain/binary-store-registry.d.ts.map +1 -0
- package/dist/domain/binary-store-registry.js +79 -0
- package/dist/domain/binary-store-registry.js.map +1 -0
- package/dist/domain/gate-registry.d.ts +53 -21
- package/dist/domain/gate-registry.d.ts.map +1 -1
- package/dist/domain/gate-registry.js +24 -5
- package/dist/domain/gate-registry.js.map +1 -1
- package/dist/domain/notification-audience.d.ts +27 -0
- package/dist/domain/notification-audience.d.ts.map +1 -0
- package/dist/domain/notification-audience.js +40 -0
- package/dist/domain/notification-audience.js.map +1 -0
- package/dist/domain/types.d.ts +1 -1
- package/dist/domain/types.d.ts.map +1 -1
- package/dist/domain/vcs-errors.d.ts +25 -0
- package/dist/domain/vcs-errors.d.ts.map +1 -1
- package/dist/domain/vcs-errors.js +41 -0
- package/dist/domain/vcs-errors.js.map +1 -1
- package/dist/domain/workspace-cascade.d.ts +1 -1
- package/dist/domain/workspace-cascade.d.ts.map +1 -1
- package/dist/domain/workspace-cascade.js +1 -0
- package/dist/domain/workspace-cascade.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/ports/binary-artifacts.d.ts +33 -2
- package/dist/ports/binary-artifacts.d.ts.map +1 -1
- package/dist/ports/binary-artifacts.js +27 -0
- package/dist/ports/binary-artifacts.js.map +1 -1
- package/dist/ports/index.d.ts +4 -3
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +2 -2
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/notification-channel.d.ts +98 -4
- package/dist/ports/notification-channel.d.ts.map +1 -1
- package/dist/ports/notification-channel.js +129 -2
- package/dist/ports/notification-channel.js.map +1 -1
- package/dist/ports/notification-settings-repositories.d.ts +16 -0
- package/dist/ports/notification-settings-repositories.d.ts.map +1 -0
- package/dist/ports/notification-settings-repositories.js +2 -0
- package/dist/ports/notification-settings-repositories.js.map +1 -0
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Notification, NotificationPayload, NotificationType } from '../domain/types.js';
|
|
1
|
+
import type { Notification, NotificationDeliveryChannel, NotificationPayload, NotificationType } from '../domain/types.js';
|
|
2
2
|
/**
|
|
3
3
|
* The input to raise (or re-raise) a notification. The canonical shape the engine and
|
|
4
4
|
* the gate/resolver extension seams use to surface a human-actionable notification; the
|
|
@@ -14,18 +14,112 @@ export interface RaiseNotificationInput {
|
|
|
14
14
|
body: string;
|
|
15
15
|
payload?: NotificationPayload | null;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* WHICH lifecycle edge a delivery reports. The NotificationService re-delivers a card on
|
|
19
|
+
* every transition it makes, and the transports split hard on what that means:
|
|
20
|
+
*
|
|
21
|
+
* - A STATE transport (the in-app push, the outbound webhook) carries the card's current
|
|
22
|
+
* value, so it wants every edge: a board holding an open card has to see it settle, or
|
|
23
|
+
* it renders an already-dismissed decision as still actionable until the next reload.
|
|
24
|
+
* - An ALERT transport (email, Slack) interrupts a human, so it wants the FIRST edge only.
|
|
25
|
+
* Mailing a board "Decision needed: …" after the decision was made is not a stale render,
|
|
26
|
+
* it is a wrong statement that cannot be taken back.
|
|
27
|
+
*
|
|
28
|
+
* Without this, a channel can only guess from `notification.status`, and the two edges that
|
|
29
|
+
* matter most are indistinguishable there: an escalation and a fresh raise are both `open`.
|
|
30
|
+
* It is a required parameter so a new call site fails to typecheck rather than silently
|
|
31
|
+
* picking whichever reading the channel it happens to reach assumes.
|
|
32
|
+
*
|
|
33
|
+
* The members, in lifecycle order:
|
|
34
|
+
* - `raised`: a human is being asked something they have NOT been asked. A new card, or one
|
|
35
|
+
* whose user-visible content changed.
|
|
36
|
+
* - `refreshed`: the same open card moved without a new ask. The escalation sweep flipped it
|
|
37
|
+
* red; a failed action put it back.
|
|
38
|
+
* - `settled`: the card is no longer actionable. Acted on, resolved, or auto-dismissed.
|
|
39
|
+
*
|
|
40
|
+
* Declared as a LIST rather than a bare union because one consumer has to check it at runtime:
|
|
41
|
+
* the mothership relay reads this off the wire from a node that may be a different build, and a
|
|
42
|
+
* predicate derived from the vocabulary's own members cannot fall out of step with it.
|
|
43
|
+
*/
|
|
44
|
+
export declare const NOTIFICATION_DELIVERY_REASONS: readonly ['raised', 'refreshed', 'settled'];
|
|
45
|
+
export type NotificationDeliveryReason = (typeof NOTIFICATION_DELIVERY_REASONS)[number];
|
|
46
|
+
/** Whether an arbitrary value is one of {@link NOTIFICATION_DELIVERY_REASONS}. */
|
|
47
|
+
export declare function isNotificationDeliveryReason(value: unknown): value is NotificationDeliveryReason;
|
|
48
|
+
/**
|
|
49
|
+
* Whether this edge is a NEW ask, and so the one an alert transport delivers on.
|
|
50
|
+
*
|
|
51
|
+
* The single place that judgement lives: three transports would otherwise each restate it,
|
|
52
|
+
* and a fourth would arrive stating it slightly differently. Exhaustive over the union, so
|
|
53
|
+
* adding an edge fails the build here rather than defaulting to "interrupt everyone".
|
|
54
|
+
*/
|
|
55
|
+
export declare function isAlertingDelivery(reason: NotificationDeliveryReason): boolean;
|
|
17
56
|
export interface NotificationChannel {
|
|
18
|
-
/** Deliver (or re-deliver
|
|
19
|
-
deliver(workspaceId: string, notification: Notification): Promise<void>;
|
|
57
|
+
/** Deliver (or re-deliver) a notification to this channel's medium, on `reason`'s edge. */
|
|
58
|
+
deliver(workspaceId: string, notification: Notification, reason: NotificationDeliveryReason): Promise<void>;
|
|
20
59
|
}
|
|
21
60
|
/** Fan a notification out to every configured channel, isolating per-channel failures. */
|
|
22
61
|
export declare class CompositeNotificationChannel implements NotificationChannel {
|
|
23
62
|
private readonly channels;
|
|
24
63
|
constructor(channels: NotificationChannel[]);
|
|
25
|
-
deliver(workspaceId: string, notification: Notification): Promise<void>;
|
|
64
|
+
deliver(workspaceId: string, notification: Notification, reason: NotificationDeliveryReason): Promise<void>;
|
|
26
65
|
}
|
|
27
66
|
/** The no-op channel: delivers nothing (tests, or a deployment with no channels wired). */
|
|
28
67
|
export declare class NoopNotificationChannel implements NotificationChannel {
|
|
29
68
|
deliver(): Promise<void>;
|
|
30
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether a workspace routes a notification type to one channel — the notification
|
|
72
|
+
* manager's decision, as the delivery path sees it. The implementation reads the
|
|
73
|
+
* workspace's stored overrides and falls back to the shipped default
|
|
74
|
+
* (`isNotificationRouted` in `@cat-factory/contracts`, the single source of truth the
|
|
75
|
+
* settings UI renders from).
|
|
76
|
+
*/
|
|
77
|
+
export interface NotificationRouter {
|
|
78
|
+
isRouted(workspaceId: string, type: NotificationType, channel: NotificationDeliveryChannel): Promise<boolean>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Gates one channel's deliveries on the workspace's routing for that channel.
|
|
82
|
+
*
|
|
83
|
+
* A DECORATOR rather than a filter inside {@link CompositeNotificationChannel}, because
|
|
84
|
+
* only some transports are routed here: Slack and the outbound webhooks answer "which
|
|
85
|
+
* types" where their destination is declared (a Slack route's channel, a webhook
|
|
86
|
+
* endpoint's own `types` filter), so wrapping them would be a second switch for a
|
|
87
|
+
* question already decided. A facade wraps exactly the channels the manager owns, and
|
|
88
|
+
* what it wrapped is legible at the wiring site.
|
|
89
|
+
*
|
|
90
|
+
* It gates the ALERTING edge ONLY (see {@link NotificationDeliveryReason}). Two things
|
|
91
|
+
* follow from that, and both are the point rather than a concession:
|
|
92
|
+
*
|
|
93
|
+
* - A mute stops the interruption, never a correction. The card is persisted whatever the
|
|
94
|
+
* routing says and reaches every board on its next snapshot, so withholding the SETTLED
|
|
95
|
+
* push would leave those boards rendering a decision that was already made as still
|
|
96
|
+
* waiting for one, curable only by a reload.
|
|
97
|
+
* - The routing read happens on the raise and nowhere else. The escalation sweep re-delivers
|
|
98
|
+
* every overdue card in a workspace in one loop; a gate that consulted the store per card
|
|
99
|
+
* would be a read per card per channel, and one mothership round trip each.
|
|
100
|
+
*
|
|
101
|
+
* What remains is one read per raised card per routed channel. That is deliberate rather
|
|
102
|
+
* than un-batched: the two routed channels land in DIFFERENT channel sets (the in-app push is
|
|
103
|
+
* local, email is external and mothership-delivered), so there is no single point that could
|
|
104
|
+
* ask once for both, and a raise is a human-scale event.
|
|
105
|
+
*
|
|
106
|
+
* A router failure means the decision is UNKNOWN, and the honest reading of that is the
|
|
107
|
+
* SHIPPED DEFAULT rather than silence: a settings read failing must not be the reason a
|
|
108
|
+
* human never hears that their run parked, and it must not turn every muted type into a
|
|
109
|
+
* mailshot either. So a throw falls back to the same default a workspace that never
|
|
110
|
+
* configured anything gets, and is reported through `onError` instead of swallowed.
|
|
111
|
+
*/
|
|
112
|
+
export declare class RoutedNotificationChannel implements NotificationChannel {
|
|
113
|
+
private readonly channel;
|
|
114
|
+
private readonly router;
|
|
115
|
+
private readonly inner;
|
|
116
|
+
private readonly onError?;
|
|
117
|
+
constructor(channel: NotificationDeliveryChannel, router: NotificationRouter, inner: NotificationChannel, onError?: ((error: unknown, context: {
|
|
118
|
+
workspaceId: string;
|
|
119
|
+
notificationId: string;
|
|
120
|
+
channel: NotificationDeliveryChannel;
|
|
121
|
+
}) => void) | undefined);
|
|
122
|
+
deliver(workspaceId: string, notification: Notification, reason: NotificationDeliveryReason): Promise<void>;
|
|
123
|
+
private routes;
|
|
124
|
+
}
|
|
31
125
|
//# sourceMappingURL=notification-channel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-channel.d.ts","sourceRoot":"","sources":["../../src/ports/notification-channel.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"notification-channel.d.ts","sourceRoot":"","sources":["../../src/ports/notification-channel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,2BAA2B,EAC3B,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,oBAAoB,CAAA;AAE3B;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAA;CACrC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,6BAA6B,YAAI,QAAQ,EAAE,WAAW,EAAE,SAAS,CAAU,CAAA;AAExF,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvF,kFAAkF;AAClF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAU9E;AAMD,MAAM,WAAW,mBAAmB;IAClC,2FAA2F;IAC3F,OAAO,CACL,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAED,0FAA0F;AAC1F,qBAAa,4BAA6B,YAAW,mBAAmB;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,EAAE,mBAAmB,EAAE,EAAI;IAE1D,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAUf;CACF;AAED,2FAA2F;AAC3F,qBAAa,uBAAwB,YAAW,mBAAmB;IAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CACN,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,OAAO,CAAC,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,yBAA0B,YAAW,mBAAmB;IAEjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAJ3B,YACmB,OAAO,EAAE,2BAA2B,EACpC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,CAAC,GAAE,CACzB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;QACP,WAAW,EAAE,MAAM,CAAA;QACnB,cAAc,EAAE,MAAM,CAAA;QACtB,OAAO,EAAE,2BAA2B,CAAA;KACrC,KACE,IAAI,aAAA,EACP;IAEE,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAGf;YAEa,MAAM;CAYrB"}
|
|
@@ -1,13 +1,78 @@
|
|
|
1
|
+
import { defaultNotificationRoute } from '@cat-factory/contracts';
|
|
2
|
+
// Port for *delivering* a notification to humans. The NotificationService owns
|
|
3
|
+
// the canonical persistence + lifecycle (raise / list / resolve); a channel is
|
|
4
|
+
// purely "how a human is told". This is the extension seam for future delivery
|
|
5
|
+
// mechanisms: in-app (push the `notification` WorkspaceEvent to the board) is the
|
|
6
|
+
// only channel today, but an EmailNotificationChannel / SlackNotificationChannel
|
|
7
|
+
// implement the same port and are composed in via CompositeNotificationChannel —
|
|
8
|
+
// no change to the call sites that raise notifications.
|
|
9
|
+
//
|
|
10
|
+
// All deliveries are best-effort: a channel failure must never break the state
|
|
11
|
+
// transition that raised the notification (the row is already persisted). Channels
|
|
12
|
+
// swallow their own errors, exactly like the event publisher.
|
|
13
|
+
/**
|
|
14
|
+
* WHICH lifecycle edge a delivery reports. The NotificationService re-delivers a card on
|
|
15
|
+
* every transition it makes, and the transports split hard on what that means:
|
|
16
|
+
*
|
|
17
|
+
* - A STATE transport (the in-app push, the outbound webhook) carries the card's current
|
|
18
|
+
* value, so it wants every edge: a board holding an open card has to see it settle, or
|
|
19
|
+
* it renders an already-dismissed decision as still actionable until the next reload.
|
|
20
|
+
* - An ALERT transport (email, Slack) interrupts a human, so it wants the FIRST edge only.
|
|
21
|
+
* Mailing a board "Decision needed: …" after the decision was made is not a stale render,
|
|
22
|
+
* it is a wrong statement that cannot be taken back.
|
|
23
|
+
*
|
|
24
|
+
* Without this, a channel can only guess from `notification.status`, and the two edges that
|
|
25
|
+
* matter most are indistinguishable there: an escalation and a fresh raise are both `open`.
|
|
26
|
+
* It is a required parameter so a new call site fails to typecheck rather than silently
|
|
27
|
+
* picking whichever reading the channel it happens to reach assumes.
|
|
28
|
+
*
|
|
29
|
+
* The members, in lifecycle order:
|
|
30
|
+
* - `raised`: a human is being asked something they have NOT been asked. A new card, or one
|
|
31
|
+
* whose user-visible content changed.
|
|
32
|
+
* - `refreshed`: the same open card moved without a new ask. The escalation sweep flipped it
|
|
33
|
+
* red; a failed action put it back.
|
|
34
|
+
* - `settled`: the card is no longer actionable. Acted on, resolved, or auto-dismissed.
|
|
35
|
+
*
|
|
36
|
+
* Declared as a LIST rather than a bare union because one consumer has to check it at runtime:
|
|
37
|
+
* the mothership relay reads this off the wire from a node that may be a different build, and a
|
|
38
|
+
* predicate derived from the vocabulary's own members cannot fall out of step with it.
|
|
39
|
+
*/
|
|
40
|
+
export const NOTIFICATION_DELIVERY_REASONS = ['raised', 'refreshed', 'settled'];
|
|
41
|
+
/** Whether an arbitrary value is one of {@link NOTIFICATION_DELIVERY_REASONS}. */
|
|
42
|
+
export function isNotificationDeliveryReason(value) {
|
|
43
|
+
return NOTIFICATION_DELIVERY_REASONS.includes(value);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Whether this edge is a NEW ask, and so the one an alert transport delivers on.
|
|
47
|
+
*
|
|
48
|
+
* The single place that judgement lives: three transports would otherwise each restate it,
|
|
49
|
+
* and a fourth would arrive stating it slightly differently. Exhaustive over the union, so
|
|
50
|
+
* adding an edge fails the build here rather than defaulting to "interrupt everyone".
|
|
51
|
+
*/
|
|
52
|
+
export function isAlertingDelivery(reason) {
|
|
53
|
+
switch (reason) {
|
|
54
|
+
case 'raised':
|
|
55
|
+
return true;
|
|
56
|
+
case 'refreshed':
|
|
57
|
+
case 'settled':
|
|
58
|
+
return false;
|
|
59
|
+
default:
|
|
60
|
+
return assertNeverDeliveryReason(reason);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function assertNeverDeliveryReason(reason) {
|
|
64
|
+
throw new Error(`unhandled notification delivery reason: ${String(reason)}`);
|
|
65
|
+
}
|
|
1
66
|
/** Fan a notification out to every configured channel, isolating per-channel failures. */
|
|
2
67
|
export class CompositeNotificationChannel {
|
|
3
68
|
channels;
|
|
4
69
|
constructor(channels) {
|
|
5
70
|
this.channels = channels;
|
|
6
71
|
}
|
|
7
|
-
async deliver(workspaceId, notification) {
|
|
72
|
+
async deliver(workspaceId, notification, reason) {
|
|
8
73
|
await Promise.all(this.channels.map(async (channel) => {
|
|
9
74
|
try {
|
|
10
|
-
await channel.deliver(workspaceId, notification);
|
|
75
|
+
await channel.deliver(workspaceId, notification, reason);
|
|
11
76
|
}
|
|
12
77
|
catch {
|
|
13
78
|
// Best-effort: one channel failing must not block the others or the caller.
|
|
@@ -19,4 +84,66 @@ export class CompositeNotificationChannel {
|
|
|
19
84
|
export class NoopNotificationChannel {
|
|
20
85
|
async deliver() { }
|
|
21
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Gates one channel's deliveries on the workspace's routing for that channel.
|
|
89
|
+
*
|
|
90
|
+
* A DECORATOR rather than a filter inside {@link CompositeNotificationChannel}, because
|
|
91
|
+
* only some transports are routed here: Slack and the outbound webhooks answer "which
|
|
92
|
+
* types" where their destination is declared (a Slack route's channel, a webhook
|
|
93
|
+
* endpoint's own `types` filter), so wrapping them would be a second switch for a
|
|
94
|
+
* question already decided. A facade wraps exactly the channels the manager owns, and
|
|
95
|
+
* what it wrapped is legible at the wiring site.
|
|
96
|
+
*
|
|
97
|
+
* It gates the ALERTING edge ONLY (see {@link NotificationDeliveryReason}). Two things
|
|
98
|
+
* follow from that, and both are the point rather than a concession:
|
|
99
|
+
*
|
|
100
|
+
* - A mute stops the interruption, never a correction. The card is persisted whatever the
|
|
101
|
+
* routing says and reaches every board on its next snapshot, so withholding the SETTLED
|
|
102
|
+
* push would leave those boards rendering a decision that was already made as still
|
|
103
|
+
* waiting for one, curable only by a reload.
|
|
104
|
+
* - The routing read happens on the raise and nowhere else. The escalation sweep re-delivers
|
|
105
|
+
* every overdue card in a workspace in one loop; a gate that consulted the store per card
|
|
106
|
+
* would be a read per card per channel, and one mothership round trip each.
|
|
107
|
+
*
|
|
108
|
+
* What remains is one read per raised card per routed channel. That is deliberate rather
|
|
109
|
+
* than un-batched: the two routed channels land in DIFFERENT channel sets (the in-app push is
|
|
110
|
+
* local, email is external and mothership-delivered), so there is no single point that could
|
|
111
|
+
* ask once for both, and a raise is a human-scale event.
|
|
112
|
+
*
|
|
113
|
+
* A router failure means the decision is UNKNOWN, and the honest reading of that is the
|
|
114
|
+
* SHIPPED DEFAULT rather than silence: a settings read failing must not be the reason a
|
|
115
|
+
* human never hears that their run parked, and it must not turn every muted type into a
|
|
116
|
+
* mailshot either. So a throw falls back to the same default a workspace that never
|
|
117
|
+
* configured anything gets, and is reported through `onError` instead of swallowed.
|
|
118
|
+
*/
|
|
119
|
+
export class RoutedNotificationChannel {
|
|
120
|
+
channel;
|
|
121
|
+
router;
|
|
122
|
+
inner;
|
|
123
|
+
onError;
|
|
124
|
+
constructor(channel, router, inner, onError) {
|
|
125
|
+
this.channel = channel;
|
|
126
|
+
this.router = router;
|
|
127
|
+
this.inner = inner;
|
|
128
|
+
this.onError = onError;
|
|
129
|
+
}
|
|
130
|
+
async deliver(workspaceId, notification, reason) {
|
|
131
|
+
if (isAlertingDelivery(reason) && !(await this.routes(workspaceId, notification)))
|
|
132
|
+
return;
|
|
133
|
+
await this.inner.deliver(workspaceId, notification, reason);
|
|
134
|
+
}
|
|
135
|
+
async routes(workspaceId, notification) {
|
|
136
|
+
try {
|
|
137
|
+
return await this.router.isRouted(workspaceId, notification.type, this.channel);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
this.onError?.(error, {
|
|
141
|
+
workspaceId,
|
|
142
|
+
notificationId: notification.id,
|
|
143
|
+
channel: this.channel,
|
|
144
|
+
});
|
|
145
|
+
return defaultNotificationRoute(notification.type, this.channel);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
22
149
|
//# sourceMappingURL=notification-channel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-channel.js","sourceRoot":"","sources":["../../src/ports/notification-channel.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"notification-channel.js","sourceRoot":"","sources":["../../src/ports/notification-channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NotificationRoutingMatrix } from '../domain/types.js';
|
|
2
|
+
export interface NotificationSettingsRecord {
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
/** The {@link NotificationRoutingMatrix}, serialized as JSON. */
|
|
5
|
+
matrixJson: string;
|
|
6
|
+
updatedAt: number;
|
|
7
|
+
}
|
|
8
|
+
export interface NotificationSettingsRepository {
|
|
9
|
+
/** A workspace's routing overrides, or null when it has never configured any. */
|
|
10
|
+
getByWorkspace(workspaceId: string): Promise<NotificationSettingsRecord | null>;
|
|
11
|
+
/** Create or replace a workspace's routing overrides. */
|
|
12
|
+
upsert(record: NotificationSettingsRecord): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/** Re-exported for repository implementations that map the persisted matrix. */
|
|
15
|
+
export type { NotificationRoutingMatrix };
|
|
16
|
+
//# sourceMappingURL=notification-settings-repositories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-settings-repositories.d.ts","sourceRoot":"","sources":["../../src/ports/notification-settings-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAYnE,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAA;IACnB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C,iFAAiF;IACjF,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAAA;IAC/E,yDAAyD;IACzD,MAAM,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,gFAAgF;AAChF,YAAY,EAAE,yBAAyB,EAAE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-settings-repositories.js","sourceRoot":"","sources":["../../src/ports/notification-settings-repositories.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.274.0",
|
|
4
4
|
"description": "Shared vocabulary, pure logic, and port interfaces for the Agent Architecture Board.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"ai": "^7.0.51",
|
|
28
28
|
"yaml": "^2.9.0",
|
|
29
|
-
"@cat-factory/contracts": "0.
|
|
29
|
+
"@cat-factory/contracts": "0.276.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@stryker-mutator/core": "9.6.1",
|