@hookflo/tern 4.1.0 → 4.2.1-beta

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 CHANGED
@@ -481,6 +481,39 @@ interface WebhookConfig {
481
481
  }
482
482
  ```
483
483
 
484
+
485
+ ## Alerting (Slack + Discord)
486
+
487
+ For the simplest DX, configure webhooks once in `createTernControls` and call `controls.alert(...)`.
488
+
489
+ ```ts
490
+ import { createTernControls } from '@hookflo/tern/upstash';
491
+
492
+ const controls = createTernControls({
493
+ token: process.env.QSTASH_TOKEN!,
494
+ notifications: {
495
+ slackWebhookUrl: process.env.SLACK_WEBHOOK_URL,
496
+ discordWebhookUrl: process.env.DISCORD_WEBHOOK_URL,
497
+ },
498
+ });
499
+
500
+ // Non-DLQ event alert with defaults
501
+ await controls.alert();
502
+
503
+ // DLQ alert + replay flow
504
+ await controls.alert({
505
+ dlq: true,
506
+ dlqId: 'dlq_xxx',
507
+ });
508
+ ```
509
+
510
+ ### Behavior
511
+
512
+ - `controls.alert()` with no params sends a normal (non-DLQ) alert with internal defaults.
513
+ - `controls.alert({ dlq: true, dlqId })` sends a DLQ alert and attempts replay internally via `controls.replay(dlqId)`.
514
+ - `eventId` is auto-filled from `dlqId` for DLQ alerts.
515
+ - Optional overrides like `message`, `metadata`, `source`, or `branding` can still be passed.
516
+
484
517
  ## Testing
485
518
 
486
519
  ### Run All Tests
@@ -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
+ __exportStar(require("./notifications"), exports);
19
+ const notifications_1 = require("./notifications");
20
+ // Backward-compatible alias used by previous version internals.
21
+ exports.__alertInternals = notifications_1.__notificationInternals;
package/dist/index.d.ts CHANGED
@@ -31,4 +31,5 @@ export { createAlgorithmVerifier } from './verifiers/algorithms';
31
31
  export { createCustomVerifier } from './verifiers/custom-algorithms';
32
32
  export { normalizePayload, getPlatformNormalizationCategory, getPlatformsByCategory, } from './normalization/simple';
33
33
  export * from './adapters';
34
+ export * from './alerts';
34
35
  export default WebhookVerificationService;
package/dist/index.js CHANGED
@@ -352,4 +352,5 @@ Object.defineProperty(exports, "normalizePayload", { enumerable: true, get: func
352
352
  Object.defineProperty(exports, "getPlatformNormalizationCategory", { enumerable: true, get: function () { return simple_2.getPlatformNormalizationCategory; } });
353
353
  Object.defineProperty(exports, "getPlatformsByCategory", { enumerable: true, get: function () { return simple_2.getPlatformsByCategory; } });
354
354
  __exportStar(require("./adapters"), exports);
355
+ __exportStar(require("./alerts"), exports);
355
356
  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,41 @@
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 fields = [
8
+ {
9
+ name: 'Severity',
10
+ value: input.severity.toUpperCase(),
11
+ inline: true,
12
+ },
13
+ ];
14
+ if (input.eventId) {
15
+ fields.push({ name: 'Event ID', value: `\`${input.eventId}\``, inline: true });
16
+ }
17
+ if (input.source) {
18
+ fields.push({ name: 'Source', value: input.source, inline: true });
19
+ }
20
+ if (input.dlq) {
21
+ fields.push({ name: 'Queue', value: 'DLQ', inline: true });
22
+ }
23
+ const metadataString = (0, utils_1.compactMetadata)(input.metadata);
24
+ if (metadataString) {
25
+ fields.push({ name: 'Details', value: `\`\`\`${metadataString}\`\`\`` });
26
+ }
27
+ const replayLine = input.replayUrl ? `\n\n[${input.replayLabel}](${input.replayUrl})` : '';
28
+ const footerText = input.branding === false ? undefined : 'Alert from Tern • tern.hookflo.com';
29
+ return {
30
+ embeds: [
31
+ {
32
+ title: input.title,
33
+ description: `${input.message}${replayLine}`,
34
+ color: parseInt(constants_1.severityColorMap[input.severity].replace('#', ''), 16),
35
+ fields,
36
+ footer: footerText ? { text: footerText } : undefined,
37
+ timestamp: new Date().toISOString(),
38
+ },
39
+ ],
40
+ };
41
+ }
@@ -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,76 @@
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 fields = [
8
+ input.eventId ? { type: 'mrkdwn', text: `*Event ID*\n\`${input.eventId}\`` } : null,
9
+ input.source ? { type: 'mrkdwn', text: `*Source*\n${input.source}` } : null,
10
+ {
11
+ type: 'mrkdwn',
12
+ text: `*Severity*\n${input.severity.toUpperCase()}`,
13
+ },
14
+ input.dlq ? { type: 'mrkdwn', text: '*Queue*\nDLQ' } : null,
15
+ ].filter(Boolean);
16
+ const blocks = [
17
+ {
18
+ type: 'header',
19
+ text: {
20
+ type: 'plain_text',
21
+ text: input.title,
22
+ emoji: true,
23
+ },
24
+ },
25
+ {
26
+ type: 'section',
27
+ text: {
28
+ type: 'mrkdwn',
29
+ text: input.message,
30
+ },
31
+ fields,
32
+ },
33
+ ];
34
+ const metadataString = (0, utils_1.compactMetadata)(input.metadata);
35
+ if (metadataString) {
36
+ blocks.push({
37
+ type: 'section',
38
+ text: {
39
+ type: 'mrkdwn',
40
+ text: `*Details*\n\`\`\`${metadataString}\`\`\``,
41
+ },
42
+ });
43
+ }
44
+ if (input.replayUrl) {
45
+ blocks.push({
46
+ type: 'actions',
47
+ elements: [
48
+ {
49
+ type: 'button',
50
+ text: {
51
+ type: 'plain_text',
52
+ text: input.replayLabel,
53
+ emoji: true,
54
+ },
55
+ url: input.replayUrl,
56
+ style: 'primary',
57
+ },
58
+ ],
59
+ });
60
+ }
61
+ if (input.branding !== false) {
62
+ blocks.push({
63
+ type: 'context',
64
+ elements: [
65
+ {
66
+ type: 'mrkdwn',
67
+ text: `Alert from <${constants_1.TERN_BRAND_URL}|Tern>`,
68
+ },
69
+ ],
70
+ });
71
+ }
72
+ return {
73
+ text: `${input.title} - ${input.message}`,
74
+ blocks,
75
+ };
76
+ }
@@ -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 = "DLQ Event Alert";
4
+ export declare const DEFAULT_ALERT_TITLE = "Webhook Event Alert";
5
+ export declare const DEFAULT_REPLAY_LABEL = "Replay DLQ Event";
6
+ export declare const DEFAULT_DLQ_MESSAGE = "Webhook event moved to DLQ after retry attempts.";
7
+ export declare const DEFAULT_ALERT_MESSAGE = "Webhook event received.";
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 = 'DLQ Event Alert';
6
+ exports.DEFAULT_ALERT_TITLE = 'Webhook Event Alert';
7
+ exports.DEFAULT_REPLAY_LABEL = 'Replay DLQ Event';
8
+ exports.DEFAULT_DLQ_MESSAGE = 'Webhook event moved to DLQ after retry attempts.';
9
+ exports.DEFAULT_ALERT_MESSAGE = 'Webhook event received.';
10
+ exports.severityColorMap = {
11
+ info: '#3B82F6',
12
+ warning: '#F59E0B',
13
+ error: '#EF4444',
14
+ critical: '#7C3AED',
15
+ };
@@ -0,0 +1,10 @@
1
+ export * from './types';
2
+ import { normalizeAlertOptions, resolveDestinations } from './utils';
3
+ import { buildSlackPayload } from './channels/slack';
4
+ import { buildDiscordPayload } from './channels/discord';
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
+ __exportStar(require("./types"), exports);
19
+ const utils_1 = require("./utils");
20
+ const slack_1 = require("./channels/slack");
21
+ const discord_1 = require("./channels/discord");
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
+ }
@@ -0,0 +1,48 @@
1
+ export type AlertSeverity = 'info' | 'warning' | 'error' | 'critical';
2
+ export type AlertChannel = 'slack' | 'discord';
3
+ export interface AlertChannelConfig {
4
+ webhookUrl: string;
5
+ enabled?: boolean;
6
+ }
7
+ export interface AlertConfig {
8
+ slack?: AlertChannelConfig;
9
+ discord?: AlertChannelConfig;
10
+ }
11
+ export interface SendAlertOptions {
12
+ dlq?: boolean;
13
+ dlqId?: string;
14
+ eventId?: string;
15
+ source?: string;
16
+ title?: string;
17
+ message?: string;
18
+ severity?: AlertSeverity;
19
+ replayUrl?: string;
20
+ replayLabel?: string;
21
+ metadata?: Record<string, unknown>;
22
+ branding?: boolean;
23
+ }
24
+ export interface SendAlertResult {
25
+ channel: AlertChannel;
26
+ webhookUrl: string;
27
+ ok: boolean;
28
+ status?: number;
29
+ error?: string;
30
+ }
31
+ export interface SendAlertSummary {
32
+ success: boolean;
33
+ total: number;
34
+ delivered: number;
35
+ results: SendAlertResult[];
36
+ }
37
+ export interface AlertDestination {
38
+ channel: AlertChannel;
39
+ webhookUrl: string;
40
+ }
41
+ export interface AlertPayloadBuilderInput extends SendAlertOptions {
42
+ title: string;
43
+ message: string;
44
+ severity: AlertSeverity;
45
+ replayLabel: string;
46
+ eventId?: string;
47
+ metadata?: Record<string, unknown>;
48
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ import type { AlertConfig, AlertDestination, AlertPayloadBuilderInput, SendAlertOptions } from './types';
2
+ export declare function compactMetadata(metadata?: Record<string, unknown>): string | undefined;
3
+ export declare function resolveDestinations(config: AlertConfig): AlertDestination[];
4
+ export declare function normalizeAlertOptions(options: SendAlertOptions): AlertPayloadBuilderInput;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compactMetadata = compactMetadata;
4
+ exports.resolveDestinations = resolveDestinations;
5
+ exports.normalizeAlertOptions = normalizeAlertOptions;
6
+ const constants_1 = require("./constants");
7
+ function compactMetadata(metadata) {
8
+ if (!metadata || Object.keys(metadata).length === 0)
9
+ return undefined;
10
+ const entries = Object.entries(metadata).slice(0, 8);
11
+ return entries.map(([key, value]) => `${key}: ${String(value)}`).join('\n');
12
+ }
13
+ function resolveDestinations(config) {
14
+ const destinations = [];
15
+ if (config.slack?.enabled !== false && config.slack?.webhookUrl) {
16
+ destinations.push({ channel: 'slack', webhookUrl: config.slack.webhookUrl });
17
+ }
18
+ if (config.discord?.enabled !== false && config.discord?.webhookUrl) {
19
+ destinations.push({ channel: 'discord', webhookUrl: config.discord.webhookUrl });
20
+ }
21
+ return destinations;
22
+ }
23
+ function normalizeAlertOptions(options) {
24
+ const isDlq = options.dlq === true;
25
+ const severity = options.severity || (isDlq ? 'error' : 'info');
26
+ const title = options.title || (isDlq ? constants_1.DEFAULT_DLQ_TITLE : constants_1.DEFAULT_ALERT_TITLE);
27
+ const message = options.message || (isDlq ? constants_1.DEFAULT_DLQ_MESSAGE : constants_1.DEFAULT_ALERT_MESSAGE);
28
+ const replayLabel = options.replayLabel || constants_1.DEFAULT_REPLAY_LABEL;
29
+ return {
30
+ ...options,
31
+ dlq: isDlq,
32
+ title,
33
+ message,
34
+ severity,
35
+ replayLabel,
36
+ eventId: options.eventId || options.dlqId,
37
+ };
38
+ }
@@ -1,6 +1,2 @@
1
- import { DLQMessage, EventFilter, ReplayResult, TernControlsConfig, TernEvent } from './types';
2
- export declare function createTernControls(config: TernControlsConfig): {
3
- dlq(): Promise<DLQMessage[]>;
4
- replay(dlqId: string): Promise<ReplayResult>;
5
- events(filter?: EventFilter): Promise<TernEvent[]>;
6
- };
1
+ import { TernControls, TernControlsConfig } from './types';
2
+ export declare function createTernControls(config: TernControlsConfig): TernControls;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createTernControls = createTernControls;
4
+ const send_alert_1 = require("../notifications/send-alert");
4
5
  const QSTASH_API_BASE = 'https://qstash.upstash.io/v2';
5
6
  function parseBody(body) {
6
7
  if (!body)
@@ -114,5 +115,45 @@ function createTernControls(config) {
114
115
  : mapped;
115
116
  return statusFiltered.slice(0, filter.limit ?? 20);
116
117
  },
118
+ async alert(options = {}) {
119
+ let replayMeta = {};
120
+ if (options.dlq) {
121
+ if (!options.dlqId || options.dlqId.trim() === '') {
122
+ throw new Error('[tern] controls.alert() with dlq: true requires dlqId.');
123
+ }
124
+ try {
125
+ const replay = await this.replay(options.dlqId);
126
+ replayMeta = {
127
+ replayAttempted: true,
128
+ replaySuccess: replay.success,
129
+ replayedAt: replay.replayedAt,
130
+ replayDlqId: options.dlqId,
131
+ };
132
+ }
133
+ catch (error) {
134
+ replayMeta = {
135
+ replayAttempted: true,
136
+ replaySuccess: false,
137
+ replayDlqId: options.dlqId,
138
+ replayError: error.message,
139
+ };
140
+ }
141
+ }
142
+ return (0, send_alert_1.sendAlert)({
143
+ slack: config.notifications?.slackWebhookUrl
144
+ ? { webhookUrl: config.notifications.slackWebhookUrl }
145
+ : undefined,
146
+ discord: config.notifications?.discordWebhookUrl
147
+ ? { webhookUrl: config.notifications.discordWebhookUrl }
148
+ : undefined,
149
+ }, {
150
+ ...options,
151
+ eventId: options.eventId || options.dlqId,
152
+ metadata: {
153
+ ...(options.metadata || {}),
154
+ ...replayMeta,
155
+ },
156
+ });
157
+ },
117
158
  };
118
159
  }
@@ -1,3 +1,3 @@
1
1
  export { createTernControls } from './controls';
2
2
  export { handleQueuedRequest, handleProcess, handleReceive, resolveQueueConfig, } from './queue';
3
- export type { DLQMessage, EventFilter, QueueOption, QueuedMessage, ReplayResult, ResolvedQueueConfig, TernControlsConfig, TernEvent, } from './types';
3
+ export type { ControlAlertOptions, DLQMessage, EventFilter, QueueOption, QueuedMessage, ReplayResult, ResolvedQueueConfig, TernControls, TernControlsConfig, TernEvent, } from './types';
@@ -1,4 +1,5 @@
1
1
  import { WebhookPlatform } from '../types';
2
+ import type { SendAlertOptions, SendAlertSummary } from '../notifications/types';
2
3
  export type QueueOption = true | {
3
4
  token: string;
4
5
  signingKey: string;
@@ -13,6 +14,10 @@ export interface ResolvedQueueConfig {
13
14
  }
14
15
  export interface TernControlsConfig {
15
16
  token: string;
17
+ notifications?: {
18
+ slackWebhookUrl?: string;
19
+ discordWebhookUrl?: string;
20
+ };
16
21
  }
17
22
  export interface DLQMessage {
18
23
  id: string;
@@ -45,3 +50,16 @@ export interface QueuedMessage {
45
50
  payload: unknown;
46
51
  metadata: Record<string, unknown>;
47
52
  }
53
+ export type ControlAlertOptions = (SendAlertOptions & {
54
+ dlq: true;
55
+ dlqId: string;
56
+ }) | (SendAlertOptions & {
57
+ dlq?: false;
58
+ dlqId?: never;
59
+ });
60
+ export interface TernControls {
61
+ dlq: () => Promise<DLQMessage[]>;
62
+ replay: (dlqId: string) => Promise<ReplayResult>;
63
+ events: (filter?: EventFilter) => Promise<TernEvent[]>;
64
+ alert: (options?: ControlAlertOptions) => Promise<SendAlertSummary>;
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hookflo/tern",
3
- "version": "4.1.0",
3
+ "version": "4.2.1-beta",
4
4
  "description": "A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",