feat(FN-2865): add notification provider abstractions
- Add shared notification event/payload/provider config types in core - Introduce notification module exports with provider interface and dispatcher implementation - Extend settings schema to accept notificationProviders configuration - Add dispatcher unit coverage for provider filtering, failures, and delivery behavior
This commit is contained in:
89
packages/core/src/notification/dispatcher.ts
Normal file
89
packages/core/src/notification/dispatcher.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { NotificationProvider } from "./provider.js";
|
||||
import type {
|
||||
NotificationDispatcherConfig,
|
||||
NotificationEvent,
|
||||
NotificationPayload,
|
||||
NotificationResult,
|
||||
} from "./types.js";
|
||||
|
||||
export class NotificationDispatcher {
|
||||
private readonly providers = new Map<string, NotificationProvider>();
|
||||
|
||||
constructor(private readonly config: NotificationDispatcherConfig = {}) {}
|
||||
|
||||
registerProvider(provider: NotificationProvider): void {
|
||||
this.providers.set(provider.getProviderId(), provider);
|
||||
}
|
||||
|
||||
unregisterProvider(providerId: string): void {
|
||||
this.providers.delete(providerId);
|
||||
}
|
||||
|
||||
getProviders(): readonly NotificationProvider[] {
|
||||
return [...this.providers.values()];
|
||||
}
|
||||
|
||||
async dispatch(
|
||||
event: NotificationEvent,
|
||||
payload: NotificationPayload,
|
||||
): Promise<NotificationResult[]> {
|
||||
const providers = this.getProviders().filter((provider) =>
|
||||
provider.isEventSupported(event),
|
||||
);
|
||||
|
||||
const results = await Promise.all(
|
||||
providers.map(async (provider): Promise<NotificationResult> => {
|
||||
const providerId = provider.getProviderId();
|
||||
try {
|
||||
return await provider.sendNotification(event, payload);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.warn(
|
||||
`[notification-dispatcher] Provider ${providerId} failed for event ${event}: ${message}`,
|
||||
);
|
||||
return { success: false, providerId, error: message };
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async initializeAll(): Promise<void> {
|
||||
await Promise.all(
|
||||
this.getProviders().map(async (provider) => {
|
||||
if (!provider.initialize) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await provider.initialize(this.config as Record<string, unknown>);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.warn(
|
||||
`[notification-dispatcher] Provider ${provider.getProviderId()} initialization failed: ${message}`,
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async shutdownAll(): Promise<void> {
|
||||
await Promise.all(
|
||||
this.getProviders().map(async (provider) => {
|
||||
if (!provider.shutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await provider.shutdown();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.warn(
|
||||
`[notification-dispatcher] Provider ${provider.getProviderId()} shutdown failed: ${message}`,
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
3
packages/core/src/notification/index.ts
Normal file
3
packages/core/src/notification/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./types.js";
|
||||
export * from "./provider.js";
|
||||
export * from "./dispatcher.js";
|
||||
13
packages/core/src/notification/provider.ts
Normal file
13
packages/core/src/notification/provider.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { NotificationEvent, NotificationPayload } from "./types.js";
|
||||
import type { NotificationResult } from "./types.js";
|
||||
|
||||
export interface NotificationProvider {
|
||||
getProviderId(): string;
|
||||
sendNotification(
|
||||
event: NotificationEvent,
|
||||
payload: NotificationPayload,
|
||||
): Promise<NotificationResult>;
|
||||
isEventSupported(event: NotificationEvent): boolean;
|
||||
initialize?(config: Record<string, unknown>): Promise<void>;
|
||||
shutdown?(): Promise<void>;
|
||||
}
|
||||
17
packages/core/src/notification/types.ts
Normal file
17
packages/core/src/notification/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export {
|
||||
NOTIFICATION_EVENTS,
|
||||
type NotificationEvent,
|
||||
type NotificationPayload,
|
||||
type NotificationProviderConfig,
|
||||
} from "../types.js";
|
||||
|
||||
export interface NotificationResult {
|
||||
success: boolean;
|
||||
providerId: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface NotificationDispatcherConfig {
|
||||
maxRetries?: number;
|
||||
retryDelayMs?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user