@hookflo/tern 3.0.5 → 3.0.9-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.
@@ -1,10 +1,12 @@
1
1
  import { WebhookPlatform, NormalizeOptions } from '../types';
2
+ import { QueueOption } from '../upstash/types';
2
3
  export interface CloudflareWebhookHandlerOptions<TEnv = Record<string, unknown>, TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
3
4
  platform: WebhookPlatform;
4
5
  secret?: string;
5
6
  secretEnv?: string;
6
7
  toleranceInSeconds?: number;
7
8
  normalize?: boolean | NormalizeOptions;
9
+ queue?: QueueOption;
8
10
  onError?: (error: Error) => void;
9
11
  handler: (payload: TPayload, env: TEnv, metadata: TMetadata) => Promise<TResponse> | TResponse;
10
12
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWebhookHandler = createWebhookHandler;
4
4
  const index_1 = require("../index");
5
+ const queue_1 = require("../upstash/queue");
5
6
  function createWebhookHandler(options) {
6
7
  return async (request, env) => {
7
8
  try {
@@ -10,6 +11,16 @@ function createWebhookHandler(options) {
10
11
  if (!secret) {
11
12
  return Response.json({ error: 'Webhook secret is not configured' }, { status: 500 });
12
13
  }
14
+ if (options.queue) {
15
+ const queueConfig = (0, queue_1.resolveQueueConfig)(options.queue);
16
+ return (0, queue_1.handleQueuedRequest)(request, {
17
+ platform: options.platform,
18
+ secret,
19
+ queueConfig,
20
+ handler: (payload, metadata) => options.handler(payload, env, metadata),
21
+ toleranceInSeconds: options.toleranceInSeconds ?? 300,
22
+ });
23
+ }
13
24
  const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, options.platform, secret, options.toleranceInSeconds, options.normalize);
14
25
  if (!result.isValid) {
15
26
  return Response.json({ error: result.error, errorCode: result.errorCode, platform: result.platform, metadata: result.metadata }, { status: 400 });
@@ -1,4 +1,5 @@
1
1
  import { WebhookPlatform, WebhookVerificationResult, NormalizeOptions } from '../types';
2
+ import { QueueOption } from '../upstash/types';
2
3
  import { MinimalNodeRequest } from './shared';
3
4
  export interface ExpressLikeResponse {
4
5
  status: (code: number) => ExpressLikeResponse;
@@ -13,6 +14,7 @@ export interface ExpressWebhookMiddlewareOptions {
13
14
  secret: string;
14
15
  toleranceInSeconds?: number;
15
16
  normalize?: boolean | NormalizeOptions;
17
+ queue?: QueueOption;
16
18
  onError?: (error: Error) => void;
17
19
  }
18
20
  export declare function createWebhookMiddleware(options: ExpressWebhookMiddlewareOptions): (req: ExpressLikeRequest, res: ExpressLikeResponse, next: ExpressLikeNext) => Promise<void>;
@@ -2,11 +2,33 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWebhookMiddleware = createWebhookMiddleware;
4
4
  const index_1 = require("../index");
5
+ const queue_1 = require("../upstash/queue");
5
6
  const shared_1 = require("./shared");
6
7
  function createWebhookMiddleware(options) {
7
8
  return async (req, res, next) => {
8
9
  try {
9
10
  const webRequest = await (0, shared_1.toWebRequest)(req);
11
+ if (options.queue) {
12
+ const queueConfig = (0, queue_1.resolveQueueConfig)(options.queue);
13
+ const queueResponse = await (0, queue_1.handleQueuedRequest)(webRequest, {
14
+ platform: options.platform,
15
+ secret: options.secret,
16
+ queueConfig,
17
+ toleranceInSeconds: options.toleranceInSeconds ?? 300,
18
+ });
19
+ const bodyText = await queueResponse.text();
20
+ let body = undefined;
21
+ if (bodyText) {
22
+ try {
23
+ body = JSON.parse(bodyText);
24
+ }
25
+ catch {
26
+ body = { message: bodyText };
27
+ }
28
+ }
29
+ res.status(queueResponse.status).json(body ?? {});
30
+ return;
31
+ }
10
32
  const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(webRequest, options.platform, options.secret, options.toleranceInSeconds, options.normalize);
11
33
  if (!result.isValid) {
12
34
  res.status(400).json({
@@ -1,9 +1,11 @@
1
1
  import { WebhookPlatform, NormalizeOptions } from '../types';
2
+ import { QueueOption } from '../upstash/types';
2
3
  export interface NextWebhookHandlerOptions<TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
3
4
  platform: WebhookPlatform;
4
5
  secret: string;
5
6
  toleranceInSeconds?: number;
6
7
  normalize?: boolean | NormalizeOptions;
8
+ queue?: QueueOption;
7
9
  onError?: (error: Error) => void;
8
10
  handler: (payload: TPayload, metadata: TMetadata) => Promise<TResponse> | TResponse;
9
11
  }
@@ -2,9 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWebhookHandler = createWebhookHandler;
4
4
  const index_1 = require("../index");
5
+ const queue_1 = require("../upstash/queue");
5
6
  function createWebhookHandler(options) {
6
7
  return async (request) => {
7
8
  try {
9
+ if (options.queue) {
10
+ const queueConfig = (0, queue_1.resolveQueueConfig)(options.queue);
11
+ return (0, queue_1.handleQueuedRequest)(request, {
12
+ platform: options.platform,
13
+ secret: options.secret,
14
+ queueConfig,
15
+ handler: options.handler,
16
+ toleranceInSeconds: options.toleranceInSeconds ?? 300,
17
+ });
18
+ }
8
19
  const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, options.platform, options.secret, options.toleranceInSeconds, options.normalize);
9
20
  if (!result.isValid) {
10
21
  return Response.json({ error: result.error, errorCode: result.errorCode, platform: result.platform, metadata: result.metadata }, { status: 400 });
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { WebhookConfig, WebhookVerificationResult, WebhookPlatform, SignatureConfig, MultiPlatformSecrets, NormalizeOptions } from './types';
2
+ import type { QueueOption } from './upstash/types';
2
3
  export declare class WebhookVerificationService {
3
4
  static verify<TPayload = unknown>(request: Request, config: WebhookConfig): Promise<WebhookVerificationResult<TPayload>>;
4
5
  private static getVerifier;
@@ -13,6 +14,13 @@ export declare class WebhookVerificationService {
13
14
  static platformUsesAlgorithm(platform: WebhookPlatform, algorithm: string): boolean;
14
15
  static validateSignatureConfig(config: SignatureConfig): boolean;
15
16
  static verifyTokenAuth<TPayload = unknown>(request: Request, webhookId: string, webhookToken: string): Promise<WebhookVerificationResult<TPayload>>;
17
+ static handleWithQueue(request: Request, options: {
18
+ platform: WebhookPlatform;
19
+ secret: string;
20
+ queue: QueueOption;
21
+ handler: (payload: unknown, metadata: Record<string, unknown>) => Promise<unknown> | unknown;
22
+ toleranceInSeconds?: number;
23
+ }): Promise<Response>;
16
24
  static verifyTokenBased<TPayload = unknown>(request: Request, webhookId: string, webhookToken: string): Promise<WebhookVerificationResult<TPayload>>;
17
25
  }
18
26
  export * from './types';
package/dist/index.js CHANGED
@@ -10,6 +10,28 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
10
10
  if (k2 === undefined) k2 = k;
11
11
  o[k2] = m[k];
12
12
  }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
13
35
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
36
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
37
  };
@@ -230,6 +252,17 @@ class WebhookVerificationService {
230
252
  };
231
253
  }
232
254
  }
255
+ static async handleWithQueue(request, options) {
256
+ const { resolveQueueConfig, handleQueuedRequest } = await Promise.resolve().then(() => __importStar(require('./upstash/queue')));
257
+ const queueConfig = resolveQueueConfig(options.queue);
258
+ return handleQueuedRequest(request, {
259
+ platform: options.platform,
260
+ secret: options.secret,
261
+ queueConfig,
262
+ handler: options.handler,
263
+ toleranceInSeconds: options.toleranceInSeconds ?? 300,
264
+ });
265
+ }
233
266
  static async verifyTokenBased(request, webhookId, webhookToken) {
234
267
  return this.verifyTokenAuth(request, webhookId, webhookToken);
235
268
  }
package/dist/test.js CHANGED
@@ -625,6 +625,41 @@ async function runTests() {
625
625
  catch (error) {
626
626
  console.log(' ❌ fal.ai test failed:', error);
627
627
  }
628
+ // Test 21: Core SDK queue entry point
629
+ console.log('\n21. Testing core SDK handleWithQueue...');
630
+ try {
631
+ const request = createMockRequest({
632
+ 'content-type': 'application/json',
633
+ });
634
+ const originalToken = process.env.QSTASH_TOKEN;
635
+ const originalCurrent = process.env.QSTASH_CURRENT_SIGNING_KEY;
636
+ const originalNext = process.env.QSTASH_NEXT_SIGNING_KEY;
637
+ delete process.env.QSTASH_TOKEN;
638
+ delete process.env.QSTASH_CURRENT_SIGNING_KEY;
639
+ delete process.env.QSTASH_NEXT_SIGNING_KEY;
640
+ let threw = false;
641
+ try {
642
+ await index_1.WebhookVerificationService.handleWithQueue(request, {
643
+ platform: 'stripe',
644
+ secret: testSecret,
645
+ queue: true,
646
+ handler: async () => undefined,
647
+ });
648
+ }
649
+ catch (error) {
650
+ threw = error.message.includes('queue: true requires QSTASH_TOKEN');
651
+ }
652
+ if (originalToken !== undefined)
653
+ process.env.QSTASH_TOKEN = originalToken;
654
+ if (originalCurrent !== undefined)
655
+ process.env.QSTASH_CURRENT_SIGNING_KEY = originalCurrent;
656
+ if (originalNext !== undefined)
657
+ process.env.QSTASH_NEXT_SIGNING_KEY = originalNext;
658
+ console.log(' ✅ handleWithQueue:', threw ? 'PASSED' : 'FAILED');
659
+ }
660
+ catch (error) {
661
+ console.log(' ❌ handleWithQueue test failed:', error);
662
+ }
628
663
  console.log('\n🎉 All tests completed!');
629
664
  }
630
665
  // Run tests if this file is executed directly
@@ -0,0 +1,6 @@
1
+ import { DLQMessage, EventFilter, ReplayResult, TernControlsConfig, TernEvent } from './types';
2
+ export declare function createTernControls(config: TernControlsConfig): {
3
+ dlq(): Promise<DLQMessage[]>;
4
+ replay(messageId: string): Promise<ReplayResult>;
5
+ events(filter?: EventFilter): Promise<TernEvent[]>;
6
+ };
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTernControls = createTernControls;
4
+ const QSTASH_API_BASE = 'https://qstash.upstash.io/v2';
5
+ function createHeaders(token) {
6
+ return {
7
+ Authorization: `Bearer ${token}`,
8
+ 'Content-Type': 'application/json',
9
+ };
10
+ }
11
+ function deriveStatus(value) {
12
+ const lowered = value.toLowerCase();
13
+ if (lowered.includes('deliver'))
14
+ return 'delivered';
15
+ if (lowered.includes('fail'))
16
+ return 'failed';
17
+ return 'retrying';
18
+ }
19
+ function createTernControls(config) {
20
+ return {
21
+ async dlq() {
22
+ const response = await fetch(`${QSTASH_API_BASE}/dlq`, {
23
+ headers: createHeaders(config.token),
24
+ });
25
+ if (!response.ok) {
26
+ throw new Error(`[tern] Failed to fetch DLQ messages: ${response.status} ${response.statusText}`);
27
+ }
28
+ const data = await response.json();
29
+ const messages = data.messages || [];
30
+ return messages.map((message) => ({
31
+ id: String(message.messageId || message.id || ''),
32
+ platform: String(message.body && typeof message.body === 'object' ? message.body.platform || 'unknown' : 'unknown'),
33
+ payload: message.body && typeof message.body === 'object' ? message.body.payload : undefined,
34
+ failedAt: String(message.updatedAt || message.createdAt || new Date().toISOString()),
35
+ attempts: Number(message.retried || message.attempts || 0),
36
+ error: typeof message.error === 'string' ? message.error : undefined,
37
+ }));
38
+ },
39
+ async replay(messageId) {
40
+ const response = await fetch(`${QSTASH_API_BASE}/dlq/${messageId}`, {
41
+ method: 'POST',
42
+ headers: createHeaders(config.token),
43
+ });
44
+ if (!response.ok) {
45
+ throw new Error(`[tern] Failed to replay DLQ message ${messageId}: ${response.status} ${response.statusText}`);
46
+ }
47
+ const data = await response.json();
48
+ return {
49
+ success: true,
50
+ messageId: String(data.messageId || messageId),
51
+ replayedAt: new Date().toISOString(),
52
+ };
53
+ },
54
+ async events(filter = {}) {
55
+ const response = await fetch(`${QSTASH_API_BASE}/messages`, {
56
+ headers: createHeaders(config.token),
57
+ });
58
+ if (!response.ok) {
59
+ throw new Error(`[tern] Failed to fetch events: ${response.status} ${response.statusText}`);
60
+ }
61
+ const data = await response.json();
62
+ const messages = data.messages || [];
63
+ const mapped = messages.map((message) => {
64
+ const status = deriveStatus(String(message.state || message.status || 'retrying'));
65
+ return {
66
+ id: String(message.messageId || message.id || ''),
67
+ platform: String(message.body && typeof message.body === 'object' ? message.body.platform || 'unknown' : 'unknown'),
68
+ status,
69
+ attempts: Number(message.retried || message.attempts || 0),
70
+ createdAt: String(message.createdAt || new Date().toISOString()),
71
+ deliveredAt: message.deliveredAt ? String(message.deliveredAt) : undefined,
72
+ };
73
+ });
74
+ const statusFiltered = filter.status
75
+ ? mapped.filter((event) => event.status === filter.status)
76
+ : mapped;
77
+ return statusFiltered.slice(0, filter.limit ?? 20);
78
+ },
79
+ };
80
+ }
@@ -0,0 +1,3 @@
1
+ export { createTernControls } from './controls';
2
+ export { handleQueuedRequest, handleProcess, handleReceive, resolveQueueConfig, } from './queue';
3
+ export type { DLQMessage, EventFilter, QueueOption, QueuedMessage, ReplayResult, ResolvedQueueConfig, TernControlsConfig, TernEvent, } from './types';
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveQueueConfig = exports.handleReceive = exports.handleProcess = exports.handleQueuedRequest = exports.createTernControls = void 0;
4
+ var controls_1 = require("./controls");
5
+ Object.defineProperty(exports, "createTernControls", { enumerable: true, get: function () { return controls_1.createTernControls; } });
6
+ var queue_1 = require("./queue");
7
+ Object.defineProperty(exports, "handleQueuedRequest", { enumerable: true, get: function () { return queue_1.handleQueuedRequest; } });
8
+ Object.defineProperty(exports, "handleProcess", { enumerable: true, get: function () { return queue_1.handleProcess; } });
9
+ Object.defineProperty(exports, "handleReceive", { enumerable: true, get: function () { return queue_1.handleReceive; } });
10
+ Object.defineProperty(exports, "resolveQueueConfig", { enumerable: true, get: function () { return queue_1.resolveQueueConfig; } });
@@ -0,0 +1,13 @@
1
+ import { WebhookPlatform } from '../types';
2
+ import { QueueOption, ResolvedQueueConfig } from './types';
3
+ export type HandlerFn = (payload: unknown, metadata: Record<string, unknown>) => Promise<unknown> | unknown;
4
+ export declare function resolveQueueConfig(queue: QueueOption): ResolvedQueueConfig;
5
+ export declare function handleReceive(request: Request, platform: WebhookPlatform, secret: string, queueConfig: ResolvedQueueConfig, toleranceInSeconds: number): Promise<Response>;
6
+ export declare function handleProcess(request: Request, handler: HandlerFn | undefined, queueConfig: ResolvedQueueConfig): Promise<Response>;
7
+ export declare function handleQueuedRequest(request: Request, options: {
8
+ platform: WebhookPlatform;
9
+ secret: string;
10
+ queueConfig: ResolvedQueueConfig;
11
+ handler?: HandlerFn;
12
+ toleranceInSeconds: number;
13
+ }): Promise<Response>;
@@ -0,0 +1,145 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveQueueConfig = resolveQueueConfig;
37
+ exports.handleReceive = handleReceive;
38
+ exports.handleProcess = handleProcess;
39
+ exports.handleQueuedRequest = handleQueuedRequest;
40
+ const QStash = __importStar(require("@upstash/qstash"));
41
+ const index_1 = require("../index");
42
+ function createQStashReceiver(queueConfig) {
43
+ const receiverExport = QStash.Receiver
44
+ ?? QStash.default?.Receiver;
45
+ if (typeof receiverExport !== 'function') {
46
+ throw new Error('[tern] Incompatible @upstash/qstash version: Receiver export not found. Please upgrade to a version that exports Receiver.');
47
+ }
48
+ return new receiverExport({
49
+ currentSigningKey: queueConfig.signingKey,
50
+ nextSigningKey: queueConfig.nextSigningKey,
51
+ });
52
+ }
53
+ function nonRetryableResponse(message, status = 489) {
54
+ return new Response(JSON.stringify({ error: message }), {
55
+ status,
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ 'Upstash-NonRetryable-Error': 'true',
59
+ },
60
+ });
61
+ }
62
+ function resolveQueueConfig(queue) {
63
+ if (queue === true) {
64
+ const token = process.env.QSTASH_TOKEN;
65
+ const signingKey = process.env.QSTASH_CURRENT_SIGNING_KEY;
66
+ const nextSigningKey = process.env.QSTASH_NEXT_SIGNING_KEY;
67
+ if (!token || !signingKey || !nextSigningKey) {
68
+ throw new Error('[tern] queue: true requires QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY in env');
69
+ }
70
+ return { token, signingKey, nextSigningKey };
71
+ }
72
+ return queue;
73
+ }
74
+ async function handleReceive(request, platform, secret, queueConfig, toleranceInSeconds) {
75
+ const verificationResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request.clone(), platform, secret, toleranceInSeconds);
76
+ if (!verificationResult.isValid) {
77
+ return nonRetryableResponse(verificationResult.error || 'Webhook signature verification failed');
78
+ }
79
+ const client = new QStash.Client({ token: queueConfig.token });
80
+ const queuedMessage = {
81
+ platform,
82
+ payload: verificationResult.payload,
83
+ metadata: verificationResult.metadata || {},
84
+ };
85
+ const idempotencyKey = request.headers.get('idempotency-key') ||
86
+ request.headers.get('x-webhook-id') ||
87
+ undefined;
88
+ const publishPayload = {
89
+ url: request.url,
90
+ body: queuedMessage,
91
+ deduplicationId: idempotencyKey,
92
+ };
93
+ if (queueConfig.retries !== undefined) {
94
+ publishPayload.retries = queueConfig.retries;
95
+ }
96
+ await client.publishJSON(publishPayload);
97
+ return new Response(JSON.stringify({ queued: true }), {
98
+ status: 200,
99
+ headers: { 'Content-Type': 'application/json' },
100
+ });
101
+ }
102
+ async function handleProcess(request, handler, queueConfig) {
103
+ const signature = request.headers.get('upstash-signature');
104
+ if (!signature) {
105
+ return new Response(JSON.stringify({ error: 'Missing Upstash-Signature header' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
106
+ }
107
+ const rawBody = await request.text();
108
+ const receiver = createQStashReceiver(queueConfig);
109
+ try {
110
+ const verification = await receiver.verify({
111
+ signature,
112
+ body: rawBody,
113
+ url: request.url,
114
+ });
115
+ if (verification === false) {
116
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
117
+ }
118
+ }
119
+ catch {
120
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
121
+ }
122
+ if (!handler) {
123
+ return nonRetryableResponse('Queue processing requires a handler function');
124
+ }
125
+ let parsedBody;
126
+ try {
127
+ parsedBody = JSON.parse(rawBody);
128
+ }
129
+ catch {
130
+ return nonRetryableResponse('Queued payload is not valid JSON');
131
+ }
132
+ try {
133
+ await handler(parsedBody.payload, parsedBody.metadata || {});
134
+ return new Response(JSON.stringify({ delivered: true }), { status: 200, headers: { 'Content-Type': 'application/json' } });
135
+ }
136
+ catch (error) {
137
+ return new Response(JSON.stringify({ error: error.message || 'Handler execution failed' }), { status: 500, headers: { 'Content-Type': 'application/json' } });
138
+ }
139
+ }
140
+ async function handleQueuedRequest(request, options) {
141
+ if (request.headers.has('upstash-signature')) {
142
+ return handleProcess(request, options.handler, options.queueConfig);
143
+ }
144
+ return handleReceive(request, options.platform, options.secret, options.queueConfig, options.toleranceInSeconds);
145
+ }
@@ -0,0 +1,46 @@
1
+ import { WebhookPlatform } from '../types';
2
+ export type QueueOption = true | {
3
+ token: string;
4
+ signingKey: string;
5
+ nextSigningKey: string;
6
+ retries?: number;
7
+ };
8
+ export interface ResolvedQueueConfig {
9
+ token: string;
10
+ signingKey: string;
11
+ nextSigningKey: string;
12
+ retries?: number;
13
+ }
14
+ export interface TernControlsConfig {
15
+ token: string;
16
+ }
17
+ export interface DLQMessage {
18
+ id: string;
19
+ platform: string;
20
+ payload: unknown;
21
+ failedAt: string;
22
+ attempts: number;
23
+ error?: string;
24
+ }
25
+ export interface ReplayResult {
26
+ success: boolean;
27
+ messageId: string;
28
+ replayedAt: string;
29
+ }
30
+ export interface EventFilter {
31
+ status?: 'delivered' | 'failed' | 'retrying';
32
+ limit?: number;
33
+ }
34
+ export interface TernEvent {
35
+ id: string;
36
+ platform: string;
37
+ status: 'delivered' | 'failed' | 'retrying';
38
+ attempts: number;
39
+ createdAt: string;
40
+ deliveredAt?: string;
41
+ }
42
+ export interface QueuedMessage {
43
+ platform: WebhookPlatform;
44
+ payload: unknown;
45
+ metadata: Record<string, unknown>;
46
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hookflo/tern",
3
- "version": "3.0.5",
3
+ "version": "3.0.9-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",
@@ -50,6 +50,7 @@
50
50
  "@types/node": "20.0.0",
51
51
  "@typescript-eslint/eslint-plugin": "^8.39.0",
52
52
  "@typescript-eslint/parser": "^8.39.0",
53
+ "@upstash/qstash": "^2.9.0",
53
54
  "eslint": "^8.57.1",
54
55
  "eslint-config-airbnb-base": "^15.0.0",
55
56
  "eslint-plugin-import": "^2.32.0",
@@ -96,7 +97,13 @@
96
97
  "import": "./dist/cloudflare.js",
97
98
  "default": "./dist/cloudflare.js"
98
99
  },
99
- "./package.json": "./package.json"
100
+ "./package.json": "./package.json",
101
+ "./upstash": {
102
+ "types": "./dist/upstash/index.d.ts",
103
+ "require": "./dist/upstash/index.js",
104
+ "import": "./dist/upstash/index.js",
105
+ "default": "./dist/upstash/index.js"
106
+ }
100
107
  },
101
108
  "typesVersions": {
102
109
  "*": {
@@ -111,7 +118,18 @@
111
118
  ],
112
119
  "*": [
113
120
  "dist/index.d.ts"
121
+ ],
122
+ "upstash": [
123
+ "dist/upstash/index.d.ts"
114
124
  ]
115
125
  }
126
+ },
127
+ "peerDependencies": {
128
+ "@upstash/qstash": ">=2.0.0"
129
+ },
130
+ "peerDependenciesMeta": {
131
+ "@upstash/qstash": {
132
+ "optional": true
133
+ }
116
134
  }
117
135
  }