@hookflo/tern 4.1.1 → 4.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.
Files changed (37) hide show
  1. package/README.md +256 -438
  2. package/dist/adapters/cloudflare.d.ts +3 -0
  3. package/dist/adapters/cloudflare.js +25 -1
  4. package/dist/adapters/express.d.ts +3 -0
  5. package/dist/adapters/express.js +16 -0
  6. package/dist/adapters/nextjs.d.ts +3 -0
  7. package/dist/adapters/nextjs.js +25 -1
  8. package/dist/alerts.d.ts +7 -0
  9. package/dist/alerts.js +21 -0
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.js +20 -1
  12. package/dist/notifications/channels/discord/build-payload.d.ts +17 -0
  13. package/dist/notifications/channels/discord/build-payload.js +53 -0
  14. package/dist/notifications/channels/discord/index.d.ts +1 -0
  15. package/dist/notifications/channels/discord/index.js +5 -0
  16. package/dist/notifications/channels/slack/build-payload.d.ts +5 -0
  17. package/dist/notifications/channels/slack/build-payload.js +82 -0
  18. package/dist/notifications/channels/slack/index.d.ts +1 -0
  19. package/dist/notifications/channels/slack/index.js +5 -0
  20. package/dist/notifications/constants.d.ts +8 -0
  21. package/dist/notifications/constants.js +15 -0
  22. package/dist/notifications/dispatch.d.ts +8 -0
  23. package/dist/notifications/dispatch.js +13 -0
  24. package/dist/notifications/index.d.ts +10 -0
  25. package/dist/notifications/index.js +27 -0
  26. package/dist/notifications/send-alert.d.ts +2 -0
  27. package/dist/notifications/send-alert.js +69 -0
  28. package/dist/notifications/types.d.ts +48 -0
  29. package/dist/notifications/types.js +2 -0
  30. package/dist/notifications/utils.d.ts +4 -0
  31. package/dist/notifications/utils.js +80 -0
  32. package/dist/upstash/controls.d.ts +2 -6
  33. package/dist/upstash/controls.js +63 -0
  34. package/dist/upstash/index.d.ts +1 -1
  35. package/dist/upstash/queue.js +5 -1
  36. package/dist/upstash/types.d.ts +18 -0
  37. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { WebhookPlatform, NormalizeOptions } from '../types';
2
2
  import { QueueOption } from '../upstash/types';
3
+ import type { AlertConfig, SendAlertOptions } from '../notifications/types';
3
4
  export interface CloudflareWebhookHandlerOptions<TEnv = Record<string, unknown>, TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
4
5
  platform: WebhookPlatform;
5
6
  secret?: string;
@@ -7,6 +8,8 @@ export interface CloudflareWebhookHandlerOptions<TEnv = Record<string, unknown>,
7
8
  toleranceInSeconds?: number;
8
9
  normalize?: boolean | NormalizeOptions;
9
10
  queue?: QueueOption;
11
+ alerts?: AlertConfig;
12
+ alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
10
13
  onError?: (error: Error) => void;
11
14
  handler: (payload: TPayload, env: TEnv, metadata: TMetadata) => Promise<TResponse> | TResponse;
12
15
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWebhookHandler = createWebhookHandler;
4
4
  const index_1 = require("../index");
5
5
  const queue_1 = require("../upstash/queue");
6
+ const dispatch_1 = require("../notifications/dispatch");
6
7
  function createWebhookHandler(options) {
7
8
  return async (request, env) => {
8
9
  try {
@@ -13,18 +14,41 @@ function createWebhookHandler(options) {
13
14
  }
14
15
  if (options.queue) {
15
16
  const queueConfig = (0, queue_1.resolveQueueConfig)(options.queue);
16
- return (0, queue_1.handleQueuedRequest)(request, {
17
+ const response = await (0, queue_1.handleQueuedRequest)(request, {
17
18
  platform: options.platform,
18
19
  secret,
19
20
  queueConfig,
20
21
  handler: (payload, metadata) => options.handler(payload, env, metadata),
21
22
  toleranceInSeconds: options.toleranceInSeconds ?? 300,
22
23
  });
24
+ if (response.ok) {
25
+ let eventId;
26
+ try {
27
+ const body = await response.clone().json();
28
+ eventId = typeof body.eventId === 'string' ? body.eventId : undefined;
29
+ }
30
+ catch {
31
+ eventId = undefined;
32
+ }
33
+ await (0, dispatch_1.dispatchWebhookAlert)({
34
+ alerts: options.alerts,
35
+ source: options.platform,
36
+ eventId,
37
+ alert: options.alert,
38
+ });
39
+ }
40
+ return response;
23
41
  }
24
42
  const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, options.platform, secret, options.toleranceInSeconds, options.normalize);
25
43
  if (!result.isValid) {
26
44
  return Response.json({ error: result.error, errorCode: result.errorCode, platform: result.platform, metadata: result.metadata }, { status: 400 });
27
45
  }
46
+ await (0, dispatch_1.dispatchWebhookAlert)({
47
+ alerts: options.alerts,
48
+ source: options.platform,
49
+ eventId: result.eventId,
50
+ alert: options.alert,
51
+ });
28
52
  const data = await options.handler(result.payload, env, (result.metadata || {}));
29
53
  return Response.json(data);
30
54
  }
@@ -1,6 +1,7 @@
1
1
  import { WebhookPlatform, WebhookVerificationResult, NormalizeOptions } from '../types';
2
2
  import { QueueOption } from '../upstash/types';
3
3
  import { MinimalNodeRequest } from './shared';
4
+ import type { AlertConfig, SendAlertOptions } from '../notifications/types';
4
5
  export interface ExpressLikeResponse {
5
6
  status: (code: number) => ExpressLikeResponse;
6
7
  json: (payload: unknown) => unknown;
@@ -15,6 +16,8 @@ export interface ExpressWebhookMiddlewareOptions {
15
16
  toleranceInSeconds?: number;
16
17
  normalize?: boolean | NormalizeOptions;
17
18
  queue?: QueueOption;
19
+ alerts?: AlertConfig;
20
+ alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
18
21
  onError?: (error: Error) => void;
19
22
  strictRawBody?: boolean;
20
23
  }
@@ -4,6 +4,7 @@ exports.createWebhookMiddleware = createWebhookMiddleware;
4
4
  const index_1 = require("../index");
5
5
  const queue_1 = require("../upstash/queue");
6
6
  const shared_1 = require("./shared");
7
+ const dispatch_1 = require("../notifications/dispatch");
7
8
  function createWebhookMiddleware(options) {
8
9
  return async (req, res, next) => {
9
10
  try {
@@ -36,6 +37,15 @@ function createWebhookMiddleware(options) {
36
37
  }
37
38
  }
38
39
  res.status(queueResponse.status).json(body ?? {});
40
+ if (queueResponse.ok) {
41
+ const queueResult = body && typeof body === 'object' ? body : undefined;
42
+ await (0, dispatch_1.dispatchWebhookAlert)({
43
+ alerts: options.alerts,
44
+ source: options.platform,
45
+ eventId: typeof queueResult?.eventId === 'string' ? queueResult.eventId : undefined,
46
+ alert: options.alert,
47
+ });
48
+ }
39
49
  return;
40
50
  }
41
51
  const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(webRequest, options.platform, options.secret, options.toleranceInSeconds, options.normalize);
@@ -49,6 +59,12 @@ function createWebhookMiddleware(options) {
49
59
  return;
50
60
  }
51
61
  req.webhook = result;
62
+ await (0, dispatch_1.dispatchWebhookAlert)({
63
+ alerts: options.alerts,
64
+ source: options.platform,
65
+ eventId: result.eventId,
66
+ alert: options.alert,
67
+ });
52
68
  next();
53
69
  }
54
70
  catch (error) {
@@ -1,11 +1,14 @@
1
1
  import { WebhookPlatform, NormalizeOptions } from '../types';
2
2
  import { QueueOption } from '../upstash/types';
3
+ import type { AlertConfig, SendAlertOptions } from '../notifications/types';
3
4
  export interface NextWebhookHandlerOptions<TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
4
5
  platform: WebhookPlatform;
5
6
  secret: string;
6
7
  toleranceInSeconds?: number;
7
8
  normalize?: boolean | NormalizeOptions;
8
9
  queue?: QueueOption;
10
+ alerts?: AlertConfig;
11
+ alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
9
12
  onError?: (error: Error) => void;
10
13
  handler: (payload: TPayload, metadata: TMetadata) => Promise<TResponse> | TResponse;
11
14
  }
@@ -3,23 +3,47 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWebhookHandler = createWebhookHandler;
4
4
  const index_1 = require("../index");
5
5
  const queue_1 = require("../upstash/queue");
6
+ const dispatch_1 = require("../notifications/dispatch");
6
7
  function createWebhookHandler(options) {
7
8
  return async (request) => {
8
9
  try {
9
10
  if (options.queue) {
10
11
  const queueConfig = (0, queue_1.resolveQueueConfig)(options.queue);
11
- return (0, queue_1.handleQueuedRequest)(request, {
12
+ const response = await (0, queue_1.handleQueuedRequest)(request, {
12
13
  platform: options.platform,
13
14
  secret: options.secret,
14
15
  queueConfig,
15
16
  handler: options.handler,
16
17
  toleranceInSeconds: options.toleranceInSeconds ?? 300,
17
18
  });
19
+ if (response.ok) {
20
+ let eventId;
21
+ try {
22
+ const body = await response.clone().json();
23
+ eventId = typeof body.eventId === 'string' ? body.eventId : undefined;
24
+ }
25
+ catch {
26
+ eventId = undefined;
27
+ }
28
+ await (0, dispatch_1.dispatchWebhookAlert)({
29
+ alerts: options.alerts,
30
+ source: options.platform,
31
+ eventId,
32
+ alert: options.alert,
33
+ });
34
+ }
35
+ return response;
18
36
  }
19
37
  const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, options.platform, options.secret, options.toleranceInSeconds, options.normalize);
20
38
  if (!result.isValid) {
21
39
  return Response.json({ error: result.error, errorCode: result.errorCode, platform: result.platform, metadata: result.metadata }, { status: 400 });
22
40
  }
41
+ await (0, dispatch_1.dispatchWebhookAlert)({
42
+ alerts: options.alerts,
43
+ source: options.platform,
44
+ eventId: result.eventId,
45
+ alert: options.alert,
46
+ });
23
47
  const data = await options.handler(result.payload, (result.metadata || {}));
24
48
  return Response.json(data);
25
49
  }
@@ -0,0 +1,7 @@
1
+ export * from './notifications';
2
+ export declare const alertInternals: {
3
+ resolveDestinations: typeof import("./notifications/utils").resolveDestinations;
4
+ normalizeAlertOptions: typeof import("./notifications/utils").normalizeAlertOptions;
5
+ buildSlackPayload: typeof import("./notifications/channels/slack").buildSlackPayload;
6
+ buildDiscordPayload: typeof import("./notifications/channels/discord").buildDiscordPayload;
7
+ };
package/dist/alerts.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.alertInternals = void 0;
18
+ const notifications_1 = require("./notifications");
19
+ __exportStar(require("./notifications"), exports);
20
+ // Backward-compatible alias used by previous version internals.
21
+ exports.alertInternals = notifications_1.notificationInternals;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { WebhookConfig, WebhookVerificationResult, WebhookPlatform, SignatureConfig, MultiPlatformSecrets, NormalizeOptions } from './types';
2
2
  import type { QueueOption } from './upstash/types';
3
+ import type { AlertConfig, SendAlertOptions } from './notifications/types';
3
4
  export declare class WebhookVerificationService {
4
5
  static verify<TPayload = unknown>(request: Request, config: WebhookConfig): Promise<WebhookVerificationResult<TPayload>>;
5
6
  private static getVerifier;
@@ -20,6 +21,8 @@ export declare class WebhookVerificationService {
20
21
  platform: WebhookPlatform;
21
22
  secret: string;
22
23
  queue: QueueOption;
24
+ alerts?: AlertConfig;
25
+ alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
23
26
  handler: (payload: unknown, metadata: Record<string, unknown>) => Promise<unknown> | unknown;
24
27
  toleranceInSeconds?: number;
25
28
  }): Promise<Response>;
@@ -31,4 +34,5 @@ export { createAlgorithmVerifier } from './verifiers/algorithms';
31
34
  export { createCustomVerifier } from './verifiers/custom-algorithms';
32
35
  export { normalizePayload, getPlatformNormalizationCategory, getPlatformsByCategory, } from './normalization/simple';
33
36
  export * from './adapters';
37
+ export * from './alerts';
34
38
  export default WebhookVerificationService;
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ const algorithms_1 = require("./verifiers/algorithms");
42
42
  const custom_algorithms_1 = require("./verifiers/custom-algorithms");
43
43
  const algorithms_2 = require("./platforms/algorithms");
44
44
  const simple_1 = require("./normalization/simple");
45
+ const dispatch_1 = require("./notifications/dispatch");
45
46
  class WebhookVerificationService {
46
47
  static async verify(request, config) {
47
48
  const verifier = this.getVerifier(config);
@@ -324,13 +325,30 @@ class WebhookVerificationService {
324
325
  static async handleWithQueue(request, options) {
325
326
  const { resolveQueueConfig, handleQueuedRequest } = await Promise.resolve().then(() => __importStar(require('./upstash/queue')));
326
327
  const queueConfig = resolveQueueConfig(options.queue);
327
- return handleQueuedRequest(request, {
328
+ const response = await handleQueuedRequest(request, {
328
329
  platform: options.platform,
329
330
  secret: options.secret,
330
331
  queueConfig,
331
332
  handler: options.handler,
332
333
  toleranceInSeconds: options.toleranceInSeconds ?? 300,
333
334
  });
335
+ if (response.ok) {
336
+ let eventId;
337
+ try {
338
+ const body = await response.clone().json();
339
+ eventId = typeof body.eventId === 'string' ? body.eventId : undefined;
340
+ }
341
+ catch {
342
+ eventId = undefined;
343
+ }
344
+ await (0, dispatch_1.dispatchWebhookAlert)({
345
+ alerts: options.alerts,
346
+ source: options.platform,
347
+ eventId,
348
+ alert: options.alert,
349
+ });
350
+ }
351
+ return response;
334
352
  }
335
353
  static async verifyTokenBased(request, webhookId, webhookToken) {
336
354
  return this.verifyTokenAuth(request, webhookId, webhookToken);
@@ -352,4 +370,5 @@ Object.defineProperty(exports, "normalizePayload", { enumerable: true, get: func
352
370
  Object.defineProperty(exports, "getPlatformNormalizationCategory", { enumerable: true, get: function () { return simple_2.getPlatformNormalizationCategory; } });
353
371
  Object.defineProperty(exports, "getPlatformsByCategory", { enumerable: true, get: function () { return simple_2.getPlatformsByCategory; } });
354
372
  __exportStar(require("./adapters"), exports);
373
+ __exportStar(require("./alerts"), exports);
355
374
  exports.default = WebhookVerificationService;
@@ -0,0 +1,17 @@
1
+ import type { AlertPayloadBuilderInput } from "../../types";
2
+ export declare function buildDiscordPayload(input: AlertPayloadBuilderInput): {
3
+ embeds: {
4
+ title: string;
5
+ description: string;
6
+ color: number;
7
+ fields: {
8
+ name: string;
9
+ value: string;
10
+ inline?: boolean;
11
+ }[];
12
+ footer: {
13
+ text: string;
14
+ } | undefined;
15
+ timestamp: string;
16
+ }[];
17
+ };
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildDiscordPayload = buildDiscordPayload;
4
+ const utils_1 = require("../../utils");
5
+ const constants_1 = require("../../constants");
6
+ function buildDiscordPayload(input) {
7
+ const isDLQ = input.dlq;
8
+ const fallbackTitle = isDLQ ? "Dead Letter Queue — Event Failed" : "Webhook Received";
9
+ const title = input.title?.trim() ? input.title : fallbackTitle;
10
+ const fallbackDescription = isDLQ
11
+ ? "Event exhausted all retries. Manual replay required."
12
+ : "Event verified and queued for processing.";
13
+ const description = input.message?.trim() ? input.message : fallbackDescription;
14
+ const fields = [];
15
+ if (input.source) {
16
+ fields.push({ name: "Platform", value: input.source, inline: true });
17
+ }
18
+ fields.push({
19
+ name: "Severity",
20
+ value: input.severity.toLowerCase(),
21
+ inline: true,
22
+ });
23
+ if (isDLQ) {
24
+ fields.push({ name: "Queue", value: "dlq", inline: true });
25
+ }
26
+ if (input.eventId) {
27
+ fields.push({
28
+ name: isDLQ ? "DLQ ID" : "Event ID",
29
+ value: `\`${input.eventId}\``,
30
+ inline: false,
31
+ });
32
+ }
33
+ const metadataString = (0, utils_1.compactMetadata)(input.metadata);
34
+ if (metadataString) {
35
+ fields.push({ name: "Details", value: `\`\`\`${metadataString}\`\`\`` });
36
+ }
37
+ const replayLine = input.replayUrl
38
+ ? `\n\n[${input.replayLabel ?? "Replay Event"}](${input.replayUrl})`
39
+ : "";
40
+ const footerText = input.branding === false ? undefined : "Alert from Tern · tern.hookflo.com";
41
+ return {
42
+ embeds: [
43
+ {
44
+ title,
45
+ description: `${description}${replayLine}`,
46
+ color: parseInt(constants_1.severityColorMap[input.severity].replace("#", ""), 16),
47
+ fields,
48
+ footer: footerText ? { text: footerText } : undefined,
49
+ timestamp: new Date().toISOString(),
50
+ },
51
+ ],
52
+ };
53
+ }
@@ -0,0 +1 @@
1
+ export { buildDiscordPayload } from './build-payload';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildDiscordPayload = void 0;
4
+ var build_payload_1 = require("./build-payload");
5
+ Object.defineProperty(exports, "buildDiscordPayload", { enumerable: true, get: function () { return build_payload_1.buildDiscordPayload; } });
@@ -0,0 +1,5 @@
1
+ import type { AlertPayloadBuilderInput } from "../../types";
2
+ export declare function buildSlackPayload(input: AlertPayloadBuilderInput): {
3
+ text: string;
4
+ blocks: Record<string, unknown>[];
5
+ };
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSlackPayload = buildSlackPayload;
4
+ const utils_1 = require("../../utils");
5
+ const constants_1 = require("../../constants");
6
+ function buildSlackPayload(input) {
7
+ const isDLQ = input.dlq;
8
+ const fallbackTitle = isDLQ ? "Dead Letter Queue — Event Failed" : "Webhook Received";
9
+ const title = input.title?.trim() ? input.title : fallbackTitle;
10
+ const fallbackMessage = isDLQ
11
+ ? "Event exhausted all retries. Manual replay required."
12
+ : "Event verified and queued for processing.";
13
+ const message = input.message?.trim() ? input.message : fallbackMessage;
14
+ const fields = [
15
+ input.source
16
+ ? { type: "mrkdwn", text: `*Platform*\n${input.source}` }
17
+ : null,
18
+ {
19
+ type: "mrkdwn",
20
+ text: `*Severity*\n${input.severity.toLowerCase()}`,
21
+ },
22
+ isDLQ ? { type: "mrkdwn", text: "*Queue*\ndlq" } : null,
23
+ input.eventId
24
+ ? {
25
+ type: "mrkdwn",
26
+ text: `*${isDLQ ? "DLQ ID" : "Event ID"}*\n\`${input.eventId}\``,
27
+ }
28
+ : null,
29
+ ].filter(Boolean);
30
+ const blocks = [
31
+ {
32
+ type: "section",
33
+ text: {
34
+ type: "mrkdwn",
35
+ text: `*${title}*\n${message}`,
36
+ },
37
+ fields,
38
+ },
39
+ ];
40
+ const metadataString = (0, utils_1.compactMetadata)(input.metadata);
41
+ if (metadataString) {
42
+ blocks.push({
43
+ type: "section",
44
+ text: {
45
+ type: "mrkdwn",
46
+ text: `\`\`\`${metadataString}\`\`\``,
47
+ },
48
+ });
49
+ }
50
+ if (input.replayUrl) {
51
+ blocks.push({
52
+ type: "actions",
53
+ elements: [
54
+ {
55
+ type: "button",
56
+ text: {
57
+ type: "plain_text",
58
+ text: input.replayLabel ?? "Replay Event",
59
+ emoji: false,
60
+ },
61
+ url: input.replayUrl,
62
+ style: "danger",
63
+ },
64
+ ],
65
+ });
66
+ }
67
+ if (input.branding !== false) {
68
+ blocks.push({
69
+ type: "context",
70
+ elements: [
71
+ {
72
+ type: "mrkdwn",
73
+ text: `Alert from <${constants_1.TERN_BRAND_URL}|Tern>`,
74
+ },
75
+ ],
76
+ });
77
+ }
78
+ return {
79
+ text: title,
80
+ blocks,
81
+ };
82
+ }
@@ -0,0 +1 @@
1
+ export { buildSlackPayload } from './build-payload';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSlackPayload = void 0;
4
+ var build_payload_1 = require("./build-payload");
5
+ Object.defineProperty(exports, "buildSlackPayload", { enumerable: true, get: function () { return build_payload_1.buildSlackPayload; } });
@@ -0,0 +1,8 @@
1
+ import type { AlertSeverity } from './types';
2
+ export declare const TERN_BRAND_URL = "https://tern.hookflo.com";
3
+ export declare const DEFAULT_DLQ_TITLE = "Dead Letter Queue \u2014 Event Failed";
4
+ export declare const DEFAULT_ALERT_TITLE = "Webhook Received";
5
+ export declare const DEFAULT_REPLAY_LABEL = "Replay DLQ Event";
6
+ export declare const DEFAULT_DLQ_MESSAGE = "Event exhausted all retries. Manual replay required.";
7
+ export declare const DEFAULT_ALERT_MESSAGE = "Event verified and queued for processing.";
8
+ export declare const severityColorMap: Record<AlertSeverity, string>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.severityColorMap = exports.DEFAULT_ALERT_MESSAGE = exports.DEFAULT_DLQ_MESSAGE = exports.DEFAULT_REPLAY_LABEL = exports.DEFAULT_ALERT_TITLE = exports.DEFAULT_DLQ_TITLE = exports.TERN_BRAND_URL = void 0;
4
+ exports.TERN_BRAND_URL = 'https://tern.hookflo.com';
5
+ exports.DEFAULT_DLQ_TITLE = 'Dead Letter Queue — Event Failed';
6
+ exports.DEFAULT_ALERT_TITLE = 'Webhook Received';
7
+ exports.DEFAULT_REPLAY_LABEL = 'Replay DLQ Event';
8
+ exports.DEFAULT_DLQ_MESSAGE = 'Event exhausted all retries. Manual replay required.';
9
+ exports.DEFAULT_ALERT_MESSAGE = 'Event verified and queued for processing.';
10
+ exports.severityColorMap = {
11
+ info: '#3B82F6',
12
+ warning: '#F59E0B',
13
+ error: '#EF4444',
14
+ critical: '#7C3AED',
15
+ };
@@ -0,0 +1,8 @@
1
+ import type { AlertConfig, SendAlertOptions } from './types';
2
+ export interface DispatchWebhookAlertInput {
3
+ alerts?: AlertConfig;
4
+ source?: string;
5
+ eventId?: string;
6
+ alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
7
+ }
8
+ export declare function dispatchWebhookAlert(input: DispatchWebhookAlertInput): Promise<void>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dispatchWebhookAlert = dispatchWebhookAlert;
4
+ const send_alert_1 = require("./send-alert");
5
+ async function dispatchWebhookAlert(input) {
6
+ if (!input.alerts)
7
+ return;
8
+ await (0, send_alert_1.sendAlert)(input.alerts, {
9
+ ...(input.alert || {}),
10
+ source: input.source,
11
+ eventId: input.eventId,
12
+ });
13
+ }
@@ -0,0 +1,10 @@
1
+ import { normalizeAlertOptions, resolveDestinations } from './utils';
2
+ import { buildSlackPayload } from './channels/slack';
3
+ import { buildDiscordPayload } from './channels/discord';
4
+ export * from './types';
5
+ export declare const notificationInternals: {
6
+ resolveDestinations: typeof resolveDestinations;
7
+ normalizeAlertOptions: typeof normalizeAlertOptions;
8
+ buildSlackPayload: typeof buildSlackPayload;
9
+ buildDiscordPayload: typeof buildDiscordPayload;
10
+ };
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.notificationInternals = void 0;
18
+ const utils_1 = require("./utils");
19
+ const slack_1 = require("./channels/slack");
20
+ const discord_1 = require("./channels/discord");
21
+ __exportStar(require("./types"), exports);
22
+ exports.notificationInternals = {
23
+ resolveDestinations: utils_1.resolveDestinations,
24
+ normalizeAlertOptions: utils_1.normalizeAlertOptions,
25
+ buildSlackPayload: slack_1.buildSlackPayload,
26
+ buildDiscordPayload: discord_1.buildDiscordPayload,
27
+ };
@@ -0,0 +1,2 @@
1
+ import type { AlertConfig, SendAlertOptions, SendAlertSummary } from './types';
2
+ export declare function sendAlert(config: AlertConfig, options: SendAlertOptions): Promise<SendAlertSummary>;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendAlert = sendAlert;
4
+ const utils_1 = require("./utils");
5
+ const slack_1 = require("./channels/slack");
6
+ const discord_1 = require("./channels/discord");
7
+ async function postWebhook(webhookUrl, body) {
8
+ try {
9
+ const response = await fetch(webhookUrl, {
10
+ method: 'POST',
11
+ headers: {
12
+ 'content-type': 'application/json',
13
+ },
14
+ body: JSON.stringify(body),
15
+ });
16
+ if (!response.ok) {
17
+ return {
18
+ ok: false,
19
+ status: response.status,
20
+ error: `Webhook call failed with ${response.status}`,
21
+ };
22
+ }
23
+ return { ok: true, status: response.status };
24
+ }
25
+ catch (error) {
26
+ return {
27
+ ok: false,
28
+ error: error.message,
29
+ };
30
+ }
31
+ }
32
+ function buildPayload(channel, options) {
33
+ const normalized = (0, utils_1.normalizeAlertOptions)(options);
34
+ return channel === 'slack' ? (0, slack_1.buildSlackPayload)(normalized) : (0, discord_1.buildDiscordPayload)(normalized);
35
+ }
36
+ async function sendAlert(config, options) {
37
+ const destinations = (0, utils_1.resolveDestinations)(config);
38
+ if (destinations.length === 0) {
39
+ return {
40
+ success: false,
41
+ total: 0,
42
+ delivered: 0,
43
+ results: [{
44
+ channel: 'slack',
45
+ webhookUrl: '',
46
+ ok: false,
47
+ error: 'No valid alert webhook destinations configured',
48
+ }],
49
+ };
50
+ }
51
+ const results = await Promise.all(destinations.map(async (destination) => {
52
+ const payload = buildPayload(destination.channel, options);
53
+ const response = await postWebhook(destination.webhookUrl, payload);
54
+ return {
55
+ channel: destination.channel,
56
+ webhookUrl: destination.webhookUrl,
57
+ ok: response.ok,
58
+ status: response.status,
59
+ error: response.error,
60
+ };
61
+ }));
62
+ const delivered = results.filter((result) => result.ok).length;
63
+ return {
64
+ success: delivered === results.length,
65
+ total: results.length,
66
+ delivered,
67
+ results,
68
+ };
69
+ }