@appbuildersph/notifications 0.0.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/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @appbuildersph/notifications
2
+
3
+ Notification center for the App Builders PH SDK — one call fans out to every
4
+ configured channel, with per-type routing and in-memory history for the dashboard.
5
+
6
+ ## Types
7
+
8
+ `success`, `information`, `warning`, `critical`, `security`, `performance`,
9
+ `deployment`, `incident`, `ai-recommendation` — each has a convenience method
10
+ (`center.security(...)`) alongside the generic `notify(type, title, message, context?)`.
11
+
12
+ ## Channels
13
+
14
+ - `WebhookChannel` — posts the raw notification as JSON
15
+ - `SlackChannel` / `DiscordChannel` / `TeamsChannel` / `TelegramChannel` — formatted for each platform's webhook/bot API
16
+ - `ToastChannel` — in-process pub/sub (no network I/O) for the badge/dashboard UI to subscribe to
17
+
18
+ ## Usage
19
+
20
+ ```ts
21
+ import { NotificationCenter, SlackChannel, ToastChannel } from "@appbuildersph/notifications";
22
+
23
+ const toast = new ToastChannel();
24
+ const center = new NotificationCenter({
25
+ channels: [toast, new SlackChannel({ webhookUrl: process.env.SLACK_WEBHOOK_URL! })],
26
+ routing: {
27
+ security: [new SlackChannel({ webhookUrl: process.env.SECURITY_SLACK_WEBHOOK_URL! })],
28
+ },
29
+ onError: (error, channel) => logger.error(`notification delivery failed via ${channel.name}`, { error }),
30
+ });
31
+
32
+ toast.subscribe((notification) => renderToast(notification));
33
+
34
+ await center.incident("Payment webhook failing", "5 consecutive 500s from /webhooks/stripe");
35
+ center.getHistory("incident");
36
+ ```
37
+
38
+ Wire `Watcher`'s `onIncident` hook into `center.incident(...)` to get automatic
39
+ crash/incident alerts across every configured channel.
package/dist/index.cjs ADDED
@@ -0,0 +1,228 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/notification-center.ts
6
+ var NotificationCenter = class {
7
+ channels;
8
+ historySize;
9
+ routing;
10
+ onError;
11
+ history = [];
12
+ constructor(options = {}) {
13
+ this.channels = options.channels ?? [];
14
+ this.historySize = options.historySize ?? 500;
15
+ this.routing = options.routing ?? {};
16
+ this.onError = options.onError;
17
+ }
18
+ addChannel(channel) {
19
+ this.channels.push(channel);
20
+ }
21
+ async notify(type, title, message, context) {
22
+ const notification = {
23
+ id: crypto.randomUUID(),
24
+ type,
25
+ title,
26
+ message,
27
+ context,
28
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
29
+ };
30
+ this.history.push(notification);
31
+ if (this.history.length > this.historySize) this.history.shift();
32
+ const targets = this.routing[type] ?? this.channels;
33
+ await Promise.all(
34
+ targets.map(async (channel) => {
35
+ try {
36
+ await channel.send(notification);
37
+ } catch (error) {
38
+ this.onError?.(error, channel, notification);
39
+ }
40
+ })
41
+ );
42
+ return notification;
43
+ }
44
+ success(title, message, context) {
45
+ return this.notify("success", title, message, context);
46
+ }
47
+ information(title, message, context) {
48
+ return this.notify("information", title, message, context);
49
+ }
50
+ warning(title, message, context) {
51
+ return this.notify("warning", title, message, context);
52
+ }
53
+ critical(title, message, context) {
54
+ return this.notify("critical", title, message, context);
55
+ }
56
+ security(title, message, context) {
57
+ return this.notify("security", title, message, context);
58
+ }
59
+ performance(title, message, context) {
60
+ return this.notify("performance", title, message, context);
61
+ }
62
+ deployment(title, message, context) {
63
+ return this.notify("deployment", title, message, context);
64
+ }
65
+ incident(title, message, context) {
66
+ return this.notify("incident", title, message, context);
67
+ }
68
+ aiRecommendation(title, message, context) {
69
+ return this.notify("ai-recommendation", title, message, context);
70
+ }
71
+ getHistory(type) {
72
+ return type ? this.history.filter((n) => n.type === type) : [...this.history];
73
+ }
74
+ clearHistory() {
75
+ this.history = [];
76
+ }
77
+ };
78
+
79
+ // src/http.ts
80
+ async function postJson(url, body) {
81
+ const response = await fetch(url, {
82
+ method: "POST",
83
+ headers: { "content-type": "application/json" },
84
+ body: JSON.stringify(body)
85
+ });
86
+ if (!response.ok) {
87
+ throw new Error(`Notification delivery to ${url} failed with status ${response.status}`);
88
+ }
89
+ }
90
+
91
+ // src/channels/webhook.ts
92
+ var WebhookChannel = class {
93
+ constructor(options) {
94
+ this.options = options;
95
+ }
96
+ options;
97
+ name = "webhook";
98
+ async send(notification) {
99
+ await postJson(this.options.url, notification);
100
+ }
101
+ };
102
+
103
+ // src/format.ts
104
+ var EMOJI = {
105
+ success: "\u2705",
106
+ information: "\u2139\uFE0F",
107
+ warning: "\u26A0\uFE0F",
108
+ critical: "\u{1F6A8}",
109
+ security: "\u{1F6E1}\uFE0F",
110
+ performance: "\u26A1",
111
+ deployment: "\u{1F680}",
112
+ incident: "\u{1F525}",
113
+ "ai-recommendation": "\u{1F916}"
114
+ };
115
+ var COLOR_HEX = {
116
+ success: "2ECC71",
117
+ information: "3498DB",
118
+ warning: "F39C12",
119
+ critical: "E74C3C",
120
+ security: "9B59B6",
121
+ performance: "1ABC9C",
122
+ deployment: "2980B9",
123
+ incident: "C0392B",
124
+ "ai-recommendation": "8E44AD"
125
+ };
126
+ function emojiFor(type) {
127
+ return EMOJI[type];
128
+ }
129
+ function colorHexFor(type) {
130
+ return COLOR_HEX[type];
131
+ }
132
+ function colorIntFor(type) {
133
+ return parseInt(COLOR_HEX[type], 16);
134
+ }
135
+
136
+ // src/channels/slack.ts
137
+ var SlackChannel = class {
138
+ constructor(options) {
139
+ this.options = options;
140
+ }
141
+ options;
142
+ name = "slack";
143
+ async send(notification) {
144
+ await postJson(this.options.webhookUrl, {
145
+ text: `${emojiFor(notification.type)} *${notification.title}*
146
+ ${notification.message}`
147
+ });
148
+ }
149
+ };
150
+
151
+ // src/channels/discord.ts
152
+ var DiscordChannel = class {
153
+ constructor(options) {
154
+ this.options = options;
155
+ }
156
+ options;
157
+ name = "discord";
158
+ async send(notification) {
159
+ await postJson(this.options.webhookUrl, {
160
+ embeds: [
161
+ {
162
+ title: `${emojiFor(notification.type)} ${notification.title}`,
163
+ description: notification.message,
164
+ color: colorIntFor(notification.type),
165
+ timestamp: notification.timestamp
166
+ }
167
+ ]
168
+ });
169
+ }
170
+ };
171
+
172
+ // src/channels/teams.ts
173
+ var TeamsChannel = class {
174
+ constructor(options) {
175
+ this.options = options;
176
+ }
177
+ options;
178
+ name = "teams";
179
+ async send(notification) {
180
+ await postJson(this.options.webhookUrl, {
181
+ "@type": "MessageCard",
182
+ "@context": "http://schema.org/extensions",
183
+ themeColor: colorHexFor(notification.type),
184
+ title: `${emojiFor(notification.type)} ${notification.title}`,
185
+ text: notification.message
186
+ });
187
+ }
188
+ };
189
+
190
+ // src/channels/telegram.ts
191
+ var TelegramChannel = class {
192
+ constructor(options) {
193
+ this.options = options;
194
+ }
195
+ options;
196
+ name = "telegram";
197
+ async send(notification) {
198
+ const url = `https://api.telegram.org/bot${this.options.botToken}/sendMessage`;
199
+ await postJson(url, {
200
+ chat_id: this.options.chatId,
201
+ text: `${emojiFor(notification.type)} ${notification.title}
202
+ ${notification.message}`
203
+ });
204
+ }
205
+ };
206
+
207
+ // src/channels/toast.ts
208
+ var ToastChannel = class {
209
+ name = "toast";
210
+ listeners = /* @__PURE__ */ new Set();
211
+ subscribe(listener) {
212
+ this.listeners.add(listener);
213
+ return () => this.listeners.delete(listener);
214
+ }
215
+ send(notification) {
216
+ for (const listener of this.listeners) listener(notification);
217
+ }
218
+ };
219
+
220
+ exports.DiscordChannel = DiscordChannel;
221
+ exports.NotificationCenter = NotificationCenter;
222
+ exports.SlackChannel = SlackChannel;
223
+ exports.TeamsChannel = TeamsChannel;
224
+ exports.TelegramChannel = TelegramChannel;
225
+ exports.ToastChannel = ToastChannel;
226
+ exports.WebhookChannel = WebhookChannel;
227
+ //# sourceMappingURL=index.cjs.map
228
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/notification-center.ts","../src/http.ts","../src/channels/webhook.ts","../src/format.ts","../src/channels/slack.ts","../src/channels/discord.ts","../src/channels/teams.ts","../src/channels/telegram.ts","../src/channels/toast.ts"],"names":["randomUUID"],"mappings":"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","file":"index.cjs","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Channel, Notification, NotificationType } from \"./types.js\";\n\nexport interface NotificationCenterOptions {\n channels?: Channel[];\n /** Number of notifications kept in memory for the dashboard/history query. Default 500. */\n historySize?: number;\n /** Only these types are dispatched to channels. Default: all types. */\n routing?: Partial<Record<NotificationType, Channel[]>>;\n onError?: (error: unknown, channel: Channel, notification: Notification) => void;\n}\n\nexport class NotificationCenter {\n private readonly channels: Channel[];\n private readonly historySize: number;\n private readonly routing: Partial<Record<NotificationType, Channel[]>>;\n private readonly onError?: NotificationCenterOptions[\"onError\"];\n private history: Notification[] = [];\n\n constructor(options: NotificationCenterOptions = {}) {\n this.channels = options.channels ?? [];\n this.historySize = options.historySize ?? 500;\n this.routing = options.routing ?? {};\n this.onError = options.onError;\n }\n\n addChannel(channel: Channel): void {\n this.channels.push(channel);\n }\n\n async notify(\n type: NotificationType,\n title: string,\n message: string,\n context?: Record<string, unknown>,\n ): Promise<Notification> {\n const notification: Notification = {\n id: randomUUID(),\n type,\n title,\n message,\n context,\n timestamp: new Date().toISOString(),\n };\n\n this.history.push(notification);\n if (this.history.length > this.historySize) this.history.shift();\n\n const targets = this.routing[type] ?? this.channels;\n await Promise.all(\n targets.map(async (channel) => {\n try {\n await channel.send(notification);\n } catch (error) {\n this.onError?.(error, channel, notification);\n }\n }),\n );\n\n return notification;\n }\n\n success(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"success\", title, message, context);\n }\n information(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"information\", title, message, context);\n }\n warning(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"warning\", title, message, context);\n }\n critical(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"critical\", title, message, context);\n }\n security(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"security\", title, message, context);\n }\n performance(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"performance\", title, message, context);\n }\n deployment(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"deployment\", title, message, context);\n }\n incident(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"incident\", title, message, context);\n }\n aiRecommendation(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"ai-recommendation\", title, message, context);\n }\n\n getHistory(type?: NotificationType): Notification[] {\n return type ? this.history.filter((n) => n.type === type) : [...this.history];\n }\n\n clearHistory(): void {\n this.history = [];\n }\n}\n","export async function postJson(url: string, body: unknown): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Notification delivery to ${url} failed with status ${response.status}`);\n }\n}\n","import { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface WebhookChannelOptions {\n url: string;\n}\n\n/** Posts the raw notification payload as JSON — for custom/internal receivers. */\nexport class WebhookChannel implements Channel {\n readonly name = \"webhook\";\n constructor(private readonly options: WebhookChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n await postJson(this.options.url, notification);\n }\n}\n","import type { NotificationType } from \"./types.js\";\n\nconst EMOJI: Record<NotificationType, string> = {\n success: \"✅\",\n information: \"ℹ️\",\n warning: \"⚠️\",\n critical: \"🚨\",\n security: \"🛡️\",\n performance: \"⚡\",\n deployment: \"🚀\",\n incident: \"🔥\",\n \"ai-recommendation\": \"🤖\",\n};\n\nconst COLOR_HEX: Record<NotificationType, string> = {\n success: \"2ECC71\",\n information: \"3498DB\",\n warning: \"F39C12\",\n critical: \"E74C3C\",\n security: \"9B59B6\",\n performance: \"1ABC9C\",\n deployment: \"2980B9\",\n incident: \"C0392B\",\n \"ai-recommendation\": \"8E44AD\",\n};\n\nexport function emojiFor(type: NotificationType): string {\n return EMOJI[type];\n}\n\nexport function colorHexFor(type: NotificationType): string {\n return COLOR_HEX[type];\n}\n\nexport function colorIntFor(type: NotificationType): number {\n return parseInt(COLOR_HEX[type], 16);\n}\n","import { emojiFor } from \"../format.js\";\nimport { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface SlackChannelOptions {\n webhookUrl: string;\n}\n\nexport class SlackChannel implements Channel {\n readonly name = \"slack\";\n constructor(private readonly options: SlackChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n await postJson(this.options.webhookUrl, {\n text: `${emojiFor(notification.type)} *${notification.title}*\\n${notification.message}`,\n });\n }\n}\n","import { colorIntFor, emojiFor } from \"../format.js\";\nimport { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface DiscordChannelOptions {\n webhookUrl: string;\n}\n\nexport class DiscordChannel implements Channel {\n readonly name = \"discord\";\n constructor(private readonly options: DiscordChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n await postJson(this.options.webhookUrl, {\n embeds: [\n {\n title: `${emojiFor(notification.type)} ${notification.title}`,\n description: notification.message,\n color: colorIntFor(notification.type),\n timestamp: notification.timestamp,\n },\n ],\n });\n }\n}\n","import { colorHexFor, emojiFor } from \"../format.js\";\nimport { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface TeamsChannelOptions {\n webhookUrl: string;\n}\n\nexport class TeamsChannel implements Channel {\n readonly name = \"teams\";\n constructor(private readonly options: TeamsChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n await postJson(this.options.webhookUrl, {\n \"@type\": \"MessageCard\",\n \"@context\": \"http://schema.org/extensions\",\n themeColor: colorHexFor(notification.type),\n title: `${emojiFor(notification.type)} ${notification.title}`,\n text: notification.message,\n });\n }\n}\n","import { emojiFor } from \"../format.js\";\nimport { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface TelegramChannelOptions {\n botToken: string;\n chatId: string;\n}\n\nexport class TelegramChannel implements Channel {\n readonly name = \"telegram\";\n constructor(private readonly options: TelegramChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n const url = `https://api.telegram.org/bot${this.options.botToken}/sendMessage`;\n await postJson(url, {\n chat_id: this.options.chatId,\n text: `${emojiFor(notification.type)} ${notification.title}\\n${notification.message}`,\n });\n }\n}\n","import type { Channel, Notification } from \"../types.js\";\n\nexport type ToastListener = (notification: Notification) => void;\n\n/**\n * In-process channel for UI surfaces (badge/dashboard) to subscribe to and render\n * as toasts. No network I/O — just an in-memory pub/sub.\n */\nexport class ToastChannel implements Channel {\n readonly name = \"toast\";\n private readonly listeners = new Set<ToastListener>();\n\n subscribe(listener: ToastListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n send(notification: Notification): void {\n for (const listener of this.listeners) listener(notification);\n }\n}\n"]}
@@ -0,0 +1,109 @@
1
+ type NotificationType = "success" | "information" | "warning" | "critical" | "security" | "performance" | "deployment" | "incident" | "ai-recommendation";
2
+ interface Notification {
3
+ id: string;
4
+ type: NotificationType;
5
+ title: string;
6
+ message: string;
7
+ timestamp: string;
8
+ context?: Record<string, unknown>;
9
+ }
10
+ interface Channel {
11
+ name: string;
12
+ send(notification: Notification): void | Promise<void>;
13
+ }
14
+
15
+ interface NotificationCenterOptions {
16
+ channels?: Channel[];
17
+ /** Number of notifications kept in memory for the dashboard/history query. Default 500. */
18
+ historySize?: number;
19
+ /** Only these types are dispatched to channels. Default: all types. */
20
+ routing?: Partial<Record<NotificationType, Channel[]>>;
21
+ onError?: (error: unknown, channel: Channel, notification: Notification) => void;
22
+ }
23
+ declare class NotificationCenter {
24
+ private readonly channels;
25
+ private readonly historySize;
26
+ private readonly routing;
27
+ private readonly onError?;
28
+ private history;
29
+ constructor(options?: NotificationCenterOptions);
30
+ addChannel(channel: Channel): void;
31
+ notify(type: NotificationType, title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
32
+ success(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
33
+ information(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
34
+ warning(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
35
+ critical(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
36
+ security(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
37
+ performance(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
38
+ deployment(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
39
+ incident(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
40
+ aiRecommendation(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
41
+ getHistory(type?: NotificationType): Notification[];
42
+ clearHistory(): void;
43
+ }
44
+
45
+ interface WebhookChannelOptions {
46
+ url: string;
47
+ }
48
+ /** Posts the raw notification payload as JSON — for custom/internal receivers. */
49
+ declare class WebhookChannel implements Channel {
50
+ private readonly options;
51
+ readonly name = "webhook";
52
+ constructor(options: WebhookChannelOptions);
53
+ send(notification: Notification): Promise<void>;
54
+ }
55
+
56
+ interface SlackChannelOptions {
57
+ webhookUrl: string;
58
+ }
59
+ declare class SlackChannel implements Channel {
60
+ private readonly options;
61
+ readonly name = "slack";
62
+ constructor(options: SlackChannelOptions);
63
+ send(notification: Notification): Promise<void>;
64
+ }
65
+
66
+ interface DiscordChannelOptions {
67
+ webhookUrl: string;
68
+ }
69
+ declare class DiscordChannel implements Channel {
70
+ private readonly options;
71
+ readonly name = "discord";
72
+ constructor(options: DiscordChannelOptions);
73
+ send(notification: Notification): Promise<void>;
74
+ }
75
+
76
+ interface TeamsChannelOptions {
77
+ webhookUrl: string;
78
+ }
79
+ declare class TeamsChannel implements Channel {
80
+ private readonly options;
81
+ readonly name = "teams";
82
+ constructor(options: TeamsChannelOptions);
83
+ send(notification: Notification): Promise<void>;
84
+ }
85
+
86
+ interface TelegramChannelOptions {
87
+ botToken: string;
88
+ chatId: string;
89
+ }
90
+ declare class TelegramChannel implements Channel {
91
+ private readonly options;
92
+ readonly name = "telegram";
93
+ constructor(options: TelegramChannelOptions);
94
+ send(notification: Notification): Promise<void>;
95
+ }
96
+
97
+ type ToastListener = (notification: Notification) => void;
98
+ /**
99
+ * In-process channel for UI surfaces (badge/dashboard) to subscribe to and render
100
+ * as toasts. No network I/O — just an in-memory pub/sub.
101
+ */
102
+ declare class ToastChannel implements Channel {
103
+ readonly name = "toast";
104
+ private readonly listeners;
105
+ subscribe(listener: ToastListener): () => void;
106
+ send(notification: Notification): void;
107
+ }
108
+
109
+ export { type Channel, DiscordChannel, type Notification, NotificationCenter, type NotificationCenterOptions, type NotificationType, SlackChannel, TeamsChannel, TelegramChannel, ToastChannel, type ToastListener, WebhookChannel };
@@ -0,0 +1,109 @@
1
+ type NotificationType = "success" | "information" | "warning" | "critical" | "security" | "performance" | "deployment" | "incident" | "ai-recommendation";
2
+ interface Notification {
3
+ id: string;
4
+ type: NotificationType;
5
+ title: string;
6
+ message: string;
7
+ timestamp: string;
8
+ context?: Record<string, unknown>;
9
+ }
10
+ interface Channel {
11
+ name: string;
12
+ send(notification: Notification): void | Promise<void>;
13
+ }
14
+
15
+ interface NotificationCenterOptions {
16
+ channels?: Channel[];
17
+ /** Number of notifications kept in memory for the dashboard/history query. Default 500. */
18
+ historySize?: number;
19
+ /** Only these types are dispatched to channels. Default: all types. */
20
+ routing?: Partial<Record<NotificationType, Channel[]>>;
21
+ onError?: (error: unknown, channel: Channel, notification: Notification) => void;
22
+ }
23
+ declare class NotificationCenter {
24
+ private readonly channels;
25
+ private readonly historySize;
26
+ private readonly routing;
27
+ private readonly onError?;
28
+ private history;
29
+ constructor(options?: NotificationCenterOptions);
30
+ addChannel(channel: Channel): void;
31
+ notify(type: NotificationType, title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
32
+ success(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
33
+ information(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
34
+ warning(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
35
+ critical(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
36
+ security(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
37
+ performance(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
38
+ deployment(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
39
+ incident(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
40
+ aiRecommendation(title: string, message: string, context?: Record<string, unknown>): Promise<Notification>;
41
+ getHistory(type?: NotificationType): Notification[];
42
+ clearHistory(): void;
43
+ }
44
+
45
+ interface WebhookChannelOptions {
46
+ url: string;
47
+ }
48
+ /** Posts the raw notification payload as JSON — for custom/internal receivers. */
49
+ declare class WebhookChannel implements Channel {
50
+ private readonly options;
51
+ readonly name = "webhook";
52
+ constructor(options: WebhookChannelOptions);
53
+ send(notification: Notification): Promise<void>;
54
+ }
55
+
56
+ interface SlackChannelOptions {
57
+ webhookUrl: string;
58
+ }
59
+ declare class SlackChannel implements Channel {
60
+ private readonly options;
61
+ readonly name = "slack";
62
+ constructor(options: SlackChannelOptions);
63
+ send(notification: Notification): Promise<void>;
64
+ }
65
+
66
+ interface DiscordChannelOptions {
67
+ webhookUrl: string;
68
+ }
69
+ declare class DiscordChannel implements Channel {
70
+ private readonly options;
71
+ readonly name = "discord";
72
+ constructor(options: DiscordChannelOptions);
73
+ send(notification: Notification): Promise<void>;
74
+ }
75
+
76
+ interface TeamsChannelOptions {
77
+ webhookUrl: string;
78
+ }
79
+ declare class TeamsChannel implements Channel {
80
+ private readonly options;
81
+ readonly name = "teams";
82
+ constructor(options: TeamsChannelOptions);
83
+ send(notification: Notification): Promise<void>;
84
+ }
85
+
86
+ interface TelegramChannelOptions {
87
+ botToken: string;
88
+ chatId: string;
89
+ }
90
+ declare class TelegramChannel implements Channel {
91
+ private readonly options;
92
+ readonly name = "telegram";
93
+ constructor(options: TelegramChannelOptions);
94
+ send(notification: Notification): Promise<void>;
95
+ }
96
+
97
+ type ToastListener = (notification: Notification) => void;
98
+ /**
99
+ * In-process channel for UI surfaces (badge/dashboard) to subscribe to and render
100
+ * as toasts. No network I/O — just an in-memory pub/sub.
101
+ */
102
+ declare class ToastChannel implements Channel {
103
+ readonly name = "toast";
104
+ private readonly listeners;
105
+ subscribe(listener: ToastListener): () => void;
106
+ send(notification: Notification): void;
107
+ }
108
+
109
+ export { type Channel, DiscordChannel, type Notification, NotificationCenter, type NotificationCenterOptions, type NotificationType, SlackChannel, TeamsChannel, TelegramChannel, ToastChannel, type ToastListener, WebhookChannel };
package/dist/index.js ADDED
@@ -0,0 +1,220 @@
1
+ import { randomUUID } from 'crypto';
2
+
3
+ // src/notification-center.ts
4
+ var NotificationCenter = class {
5
+ channels;
6
+ historySize;
7
+ routing;
8
+ onError;
9
+ history = [];
10
+ constructor(options = {}) {
11
+ this.channels = options.channels ?? [];
12
+ this.historySize = options.historySize ?? 500;
13
+ this.routing = options.routing ?? {};
14
+ this.onError = options.onError;
15
+ }
16
+ addChannel(channel) {
17
+ this.channels.push(channel);
18
+ }
19
+ async notify(type, title, message, context) {
20
+ const notification = {
21
+ id: randomUUID(),
22
+ type,
23
+ title,
24
+ message,
25
+ context,
26
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
27
+ };
28
+ this.history.push(notification);
29
+ if (this.history.length > this.historySize) this.history.shift();
30
+ const targets = this.routing[type] ?? this.channels;
31
+ await Promise.all(
32
+ targets.map(async (channel) => {
33
+ try {
34
+ await channel.send(notification);
35
+ } catch (error) {
36
+ this.onError?.(error, channel, notification);
37
+ }
38
+ })
39
+ );
40
+ return notification;
41
+ }
42
+ success(title, message, context) {
43
+ return this.notify("success", title, message, context);
44
+ }
45
+ information(title, message, context) {
46
+ return this.notify("information", title, message, context);
47
+ }
48
+ warning(title, message, context) {
49
+ return this.notify("warning", title, message, context);
50
+ }
51
+ critical(title, message, context) {
52
+ return this.notify("critical", title, message, context);
53
+ }
54
+ security(title, message, context) {
55
+ return this.notify("security", title, message, context);
56
+ }
57
+ performance(title, message, context) {
58
+ return this.notify("performance", title, message, context);
59
+ }
60
+ deployment(title, message, context) {
61
+ return this.notify("deployment", title, message, context);
62
+ }
63
+ incident(title, message, context) {
64
+ return this.notify("incident", title, message, context);
65
+ }
66
+ aiRecommendation(title, message, context) {
67
+ return this.notify("ai-recommendation", title, message, context);
68
+ }
69
+ getHistory(type) {
70
+ return type ? this.history.filter((n) => n.type === type) : [...this.history];
71
+ }
72
+ clearHistory() {
73
+ this.history = [];
74
+ }
75
+ };
76
+
77
+ // src/http.ts
78
+ async function postJson(url, body) {
79
+ const response = await fetch(url, {
80
+ method: "POST",
81
+ headers: { "content-type": "application/json" },
82
+ body: JSON.stringify(body)
83
+ });
84
+ if (!response.ok) {
85
+ throw new Error(`Notification delivery to ${url} failed with status ${response.status}`);
86
+ }
87
+ }
88
+
89
+ // src/channels/webhook.ts
90
+ var WebhookChannel = class {
91
+ constructor(options) {
92
+ this.options = options;
93
+ }
94
+ options;
95
+ name = "webhook";
96
+ async send(notification) {
97
+ await postJson(this.options.url, notification);
98
+ }
99
+ };
100
+
101
+ // src/format.ts
102
+ var EMOJI = {
103
+ success: "\u2705",
104
+ information: "\u2139\uFE0F",
105
+ warning: "\u26A0\uFE0F",
106
+ critical: "\u{1F6A8}",
107
+ security: "\u{1F6E1}\uFE0F",
108
+ performance: "\u26A1",
109
+ deployment: "\u{1F680}",
110
+ incident: "\u{1F525}",
111
+ "ai-recommendation": "\u{1F916}"
112
+ };
113
+ var COLOR_HEX = {
114
+ success: "2ECC71",
115
+ information: "3498DB",
116
+ warning: "F39C12",
117
+ critical: "E74C3C",
118
+ security: "9B59B6",
119
+ performance: "1ABC9C",
120
+ deployment: "2980B9",
121
+ incident: "C0392B",
122
+ "ai-recommendation": "8E44AD"
123
+ };
124
+ function emojiFor(type) {
125
+ return EMOJI[type];
126
+ }
127
+ function colorHexFor(type) {
128
+ return COLOR_HEX[type];
129
+ }
130
+ function colorIntFor(type) {
131
+ return parseInt(COLOR_HEX[type], 16);
132
+ }
133
+
134
+ // src/channels/slack.ts
135
+ var SlackChannel = class {
136
+ constructor(options) {
137
+ this.options = options;
138
+ }
139
+ options;
140
+ name = "slack";
141
+ async send(notification) {
142
+ await postJson(this.options.webhookUrl, {
143
+ text: `${emojiFor(notification.type)} *${notification.title}*
144
+ ${notification.message}`
145
+ });
146
+ }
147
+ };
148
+
149
+ // src/channels/discord.ts
150
+ var DiscordChannel = class {
151
+ constructor(options) {
152
+ this.options = options;
153
+ }
154
+ options;
155
+ name = "discord";
156
+ async send(notification) {
157
+ await postJson(this.options.webhookUrl, {
158
+ embeds: [
159
+ {
160
+ title: `${emojiFor(notification.type)} ${notification.title}`,
161
+ description: notification.message,
162
+ color: colorIntFor(notification.type),
163
+ timestamp: notification.timestamp
164
+ }
165
+ ]
166
+ });
167
+ }
168
+ };
169
+
170
+ // src/channels/teams.ts
171
+ var TeamsChannel = class {
172
+ constructor(options) {
173
+ this.options = options;
174
+ }
175
+ options;
176
+ name = "teams";
177
+ async send(notification) {
178
+ await postJson(this.options.webhookUrl, {
179
+ "@type": "MessageCard",
180
+ "@context": "http://schema.org/extensions",
181
+ themeColor: colorHexFor(notification.type),
182
+ title: `${emojiFor(notification.type)} ${notification.title}`,
183
+ text: notification.message
184
+ });
185
+ }
186
+ };
187
+
188
+ // src/channels/telegram.ts
189
+ var TelegramChannel = class {
190
+ constructor(options) {
191
+ this.options = options;
192
+ }
193
+ options;
194
+ name = "telegram";
195
+ async send(notification) {
196
+ const url = `https://api.telegram.org/bot${this.options.botToken}/sendMessage`;
197
+ await postJson(url, {
198
+ chat_id: this.options.chatId,
199
+ text: `${emojiFor(notification.type)} ${notification.title}
200
+ ${notification.message}`
201
+ });
202
+ }
203
+ };
204
+
205
+ // src/channels/toast.ts
206
+ var ToastChannel = class {
207
+ name = "toast";
208
+ listeners = /* @__PURE__ */ new Set();
209
+ subscribe(listener) {
210
+ this.listeners.add(listener);
211
+ return () => this.listeners.delete(listener);
212
+ }
213
+ send(notification) {
214
+ for (const listener of this.listeners) listener(notification);
215
+ }
216
+ };
217
+
218
+ export { DiscordChannel, NotificationCenter, SlackChannel, TeamsChannel, TelegramChannel, ToastChannel, WebhookChannel };
219
+ //# sourceMappingURL=index.js.map
220
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/notification-center.ts","../src/http.ts","../src/channels/webhook.ts","../src/format.ts","../src/channels/slack.ts","../src/channels/discord.ts","../src/channels/teams.ts","../src/channels/telegram.ts","../src/channels/toast.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Channel, Notification, NotificationType } from \"./types.js\";\n\nexport interface NotificationCenterOptions {\n channels?: Channel[];\n /** Number of notifications kept in memory for the dashboard/history query. Default 500. */\n historySize?: number;\n /** Only these types are dispatched to channels. Default: all types. */\n routing?: Partial<Record<NotificationType, Channel[]>>;\n onError?: (error: unknown, channel: Channel, notification: Notification) => void;\n}\n\nexport class NotificationCenter {\n private readonly channels: Channel[];\n private readonly historySize: number;\n private readonly routing: Partial<Record<NotificationType, Channel[]>>;\n private readonly onError?: NotificationCenterOptions[\"onError\"];\n private history: Notification[] = [];\n\n constructor(options: NotificationCenterOptions = {}) {\n this.channels = options.channels ?? [];\n this.historySize = options.historySize ?? 500;\n this.routing = options.routing ?? {};\n this.onError = options.onError;\n }\n\n addChannel(channel: Channel): void {\n this.channels.push(channel);\n }\n\n async notify(\n type: NotificationType,\n title: string,\n message: string,\n context?: Record<string, unknown>,\n ): Promise<Notification> {\n const notification: Notification = {\n id: randomUUID(),\n type,\n title,\n message,\n context,\n timestamp: new Date().toISOString(),\n };\n\n this.history.push(notification);\n if (this.history.length > this.historySize) this.history.shift();\n\n const targets = this.routing[type] ?? this.channels;\n await Promise.all(\n targets.map(async (channel) => {\n try {\n await channel.send(notification);\n } catch (error) {\n this.onError?.(error, channel, notification);\n }\n }),\n );\n\n return notification;\n }\n\n success(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"success\", title, message, context);\n }\n information(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"information\", title, message, context);\n }\n warning(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"warning\", title, message, context);\n }\n critical(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"critical\", title, message, context);\n }\n security(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"security\", title, message, context);\n }\n performance(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"performance\", title, message, context);\n }\n deployment(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"deployment\", title, message, context);\n }\n incident(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"incident\", title, message, context);\n }\n aiRecommendation(title: string, message: string, context?: Record<string, unknown>) {\n return this.notify(\"ai-recommendation\", title, message, context);\n }\n\n getHistory(type?: NotificationType): Notification[] {\n return type ? this.history.filter((n) => n.type === type) : [...this.history];\n }\n\n clearHistory(): void {\n this.history = [];\n }\n}\n","export async function postJson(url: string, body: unknown): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Notification delivery to ${url} failed with status ${response.status}`);\n }\n}\n","import { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface WebhookChannelOptions {\n url: string;\n}\n\n/** Posts the raw notification payload as JSON — for custom/internal receivers. */\nexport class WebhookChannel implements Channel {\n readonly name = \"webhook\";\n constructor(private readonly options: WebhookChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n await postJson(this.options.url, notification);\n }\n}\n","import type { NotificationType } from \"./types.js\";\n\nconst EMOJI: Record<NotificationType, string> = {\n success: \"✅\",\n information: \"ℹ️\",\n warning: \"⚠️\",\n critical: \"🚨\",\n security: \"🛡️\",\n performance: \"⚡\",\n deployment: \"🚀\",\n incident: \"🔥\",\n \"ai-recommendation\": \"🤖\",\n};\n\nconst COLOR_HEX: Record<NotificationType, string> = {\n success: \"2ECC71\",\n information: \"3498DB\",\n warning: \"F39C12\",\n critical: \"E74C3C\",\n security: \"9B59B6\",\n performance: \"1ABC9C\",\n deployment: \"2980B9\",\n incident: \"C0392B\",\n \"ai-recommendation\": \"8E44AD\",\n};\n\nexport function emojiFor(type: NotificationType): string {\n return EMOJI[type];\n}\n\nexport function colorHexFor(type: NotificationType): string {\n return COLOR_HEX[type];\n}\n\nexport function colorIntFor(type: NotificationType): number {\n return parseInt(COLOR_HEX[type], 16);\n}\n","import { emojiFor } from \"../format.js\";\nimport { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface SlackChannelOptions {\n webhookUrl: string;\n}\n\nexport class SlackChannel implements Channel {\n readonly name = \"slack\";\n constructor(private readonly options: SlackChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n await postJson(this.options.webhookUrl, {\n text: `${emojiFor(notification.type)} *${notification.title}*\\n${notification.message}`,\n });\n }\n}\n","import { colorIntFor, emojiFor } from \"../format.js\";\nimport { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface DiscordChannelOptions {\n webhookUrl: string;\n}\n\nexport class DiscordChannel implements Channel {\n readonly name = \"discord\";\n constructor(private readonly options: DiscordChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n await postJson(this.options.webhookUrl, {\n embeds: [\n {\n title: `${emojiFor(notification.type)} ${notification.title}`,\n description: notification.message,\n color: colorIntFor(notification.type),\n timestamp: notification.timestamp,\n },\n ],\n });\n }\n}\n","import { colorHexFor, emojiFor } from \"../format.js\";\nimport { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface TeamsChannelOptions {\n webhookUrl: string;\n}\n\nexport class TeamsChannel implements Channel {\n readonly name = \"teams\";\n constructor(private readonly options: TeamsChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n await postJson(this.options.webhookUrl, {\n \"@type\": \"MessageCard\",\n \"@context\": \"http://schema.org/extensions\",\n themeColor: colorHexFor(notification.type),\n title: `${emojiFor(notification.type)} ${notification.title}`,\n text: notification.message,\n });\n }\n}\n","import { emojiFor } from \"../format.js\";\nimport { postJson } from \"../http.js\";\nimport type { Channel, Notification } from \"../types.js\";\n\nexport interface TelegramChannelOptions {\n botToken: string;\n chatId: string;\n}\n\nexport class TelegramChannel implements Channel {\n readonly name = \"telegram\";\n constructor(private readonly options: TelegramChannelOptions) {}\n\n async send(notification: Notification): Promise<void> {\n const url = `https://api.telegram.org/bot${this.options.botToken}/sendMessage`;\n await postJson(url, {\n chat_id: this.options.chatId,\n text: `${emojiFor(notification.type)} ${notification.title}\\n${notification.message}`,\n });\n }\n}\n","import type { Channel, Notification } from \"../types.js\";\n\nexport type ToastListener = (notification: Notification) => void;\n\n/**\n * In-process channel for UI surfaces (badge/dashboard) to subscribe to and render\n * as toasts. No network I/O — just an in-memory pub/sub.\n */\nexport class ToastChannel implements Channel {\n readonly name = \"toast\";\n private readonly listeners = new Set<ToastListener>();\n\n subscribe(listener: ToastListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n send(notification: Notification): void {\n for (const listener of this.listeners) listener(notification);\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@appbuildersph/notifications",
3
+ "version": "0.0.1",
4
+ "description": "Notification center for the App Builders PH SDK — multi-channel dispatch for alerts, incidents, and AI recommendations.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "dependencies": {
20
+ "@appbuildersph/shared": "0.0.1"
21
+ },
22
+ "devDependencies": {
23
+ "tsup": "^8.3.5",
24
+ "typescript": "^5.6.3",
25
+ "vitest": "^2.1.4"
26
+ },
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "vitest run"
32
+ }
33
+ }