@hookflo/tern 3.0.3 → 3.0.5-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
  }
@@ -1,4 +1,4 @@
1
- import { PlatformAlgorithmConfig, WebhookPlatform, SignatureConfig } from '../types';
1
+ import { PlatformAlgorithmConfig, WebhookPlatform, SignatureConfig } from "../types";
2
2
  export declare const platformAlgorithmConfigs: Record<WebhookPlatform, PlatformAlgorithmConfig>;
3
3
  export declare function getPlatformAlgorithmConfig(platform: WebhookPlatform): PlatformAlgorithmConfig;
4
4
  export declare function platformUsesAlgorithm(platform: WebhookPlatform, algorithm: string): boolean;
@@ -7,307 +7,309 @@ exports.getPlatformsUsingAlgorithm = getPlatformsUsingAlgorithm;
7
7
  exports.validateSignatureConfig = validateSignatureConfig;
8
8
  exports.platformAlgorithmConfigs = {
9
9
  github: {
10
- platform: 'github',
10
+ platform: "github",
11
11
  signatureConfig: {
12
- algorithm: 'hmac-sha256',
13
- headerName: 'x-hub-signature-256',
14
- headerFormat: 'prefixed',
15
- prefix: 'sha256=',
12
+ algorithm: "hmac-sha256",
13
+ headerName: "x-hub-signature-256",
14
+ headerFormat: "prefixed",
15
+ prefix: "sha256=",
16
16
  timestampHeader: undefined,
17
- payloadFormat: 'raw',
17
+ payloadFormat: "raw",
18
18
  },
19
- description: 'GitHub webhooks use HMAC-SHA256 with sha256= prefix',
19
+ description: "GitHub webhooks use HMAC-SHA256 with sha256= prefix",
20
20
  },
21
21
  stripe: {
22
- platform: 'stripe',
22
+ platform: "stripe",
23
23
  signatureConfig: {
24
- algorithm: 'hmac-sha256',
25
- headerName: 'stripe-signature',
26
- headerFormat: 'comma-separated',
24
+ algorithm: "hmac-sha256",
25
+ headerName: "stripe-signature",
26
+ headerFormat: "comma-separated",
27
27
  timestampHeader: undefined,
28
- payloadFormat: 'timestamped',
28
+ payloadFormat: "timestamped",
29
29
  customConfig: {
30
- signatureFormat: 't={timestamp},v1={signature}',
30
+ signatureFormat: "t={timestamp},v1={signature}",
31
31
  },
32
32
  },
33
- description: 'Stripe webhooks use HMAC-SHA256 with comma-separated format',
33
+ description: "Stripe webhooks use HMAC-SHA256 with comma-separated format",
34
34
  },
35
35
  clerk: {
36
- platform: 'clerk',
36
+ platform: "clerk",
37
37
  signatureConfig: {
38
- algorithm: 'hmac-sha256',
39
- headerName: 'svix-signature',
40
- headerFormat: 'raw',
41
- timestampHeader: 'svix-timestamp',
42
- timestampFormat: 'unix',
43
- payloadFormat: 'custom',
38
+ algorithm: "hmac-sha256",
39
+ headerName: "svix-signature",
40
+ headerFormat: "raw",
41
+ timestampHeader: "svix-timestamp",
42
+ timestampFormat: "unix",
43
+ payloadFormat: "custom",
44
44
  customConfig: {
45
- signatureFormat: 'v1={signature}',
46
- payloadFormat: '{id}.{timestamp}.{body}',
47
- encoding: 'base64',
48
- secretEncoding: 'base64',
49
- idHeader: 'svix-id',
45
+ signatureFormat: "v1={signature}",
46
+ payloadFormat: "{id}.{timestamp}.{body}",
47
+ encoding: "base64",
48
+ secretEncoding: "base64",
49
+ idHeader: "svix-id",
50
50
  },
51
51
  },
52
- description: 'Clerk webhooks use HMAC-SHA256 with base64 encoding',
52
+ description: "Clerk webhooks use HMAC-SHA256 with base64 encoding",
53
53
  },
54
54
  dodopayments: {
55
- platform: 'dodopayments',
55
+ platform: "dodopayments",
56
56
  signatureConfig: {
57
- algorithm: 'hmac-sha256',
58
- headerName: 'webhook-signature',
59
- headerFormat: 'raw',
60
- timestampHeader: 'webhook-timestamp',
61
- timestampFormat: 'unix',
62
- payloadFormat: 'custom',
57
+ algorithm: "hmac-sha256",
58
+ headerName: "webhook-signature",
59
+ headerFormat: "raw",
60
+ timestampHeader: "webhook-timestamp",
61
+ timestampFormat: "unix",
62
+ payloadFormat: "custom",
63
63
  customConfig: {
64
- signatureFormat: 'v1={signature}',
65
- payloadFormat: '{id}.{timestamp}.{body}',
66
- encoding: 'base64',
67
- secretEncoding: 'base64',
68
- idHeader: 'webhook-id',
64
+ signatureFormat: "v1={signature}",
65
+ payloadFormat: "{id}.{timestamp}.{body}",
66
+ encoding: "base64",
67
+ secretEncoding: "base64",
68
+ idHeader: "webhook-id",
69
69
  },
70
70
  },
71
- description: 'Dodo Payments webhooks use HMAC-SHA256 with svix-style format (Standard Webhooks)',
71
+ description: "Dodo Payments webhooks use HMAC-SHA256 with svix-style format (Standard Webhooks)",
72
72
  },
73
73
  shopify: {
74
- platform: 'shopify',
74
+ platform: "shopify",
75
75
  signatureConfig: {
76
- algorithm: 'hmac-sha256',
77
- headerName: 'x-shopify-hmac-sha256',
78
- headerFormat: 'raw',
79
- payloadFormat: 'raw',
76
+ algorithm: "hmac-sha256",
77
+ headerName: "x-shopify-hmac-sha256",
78
+ headerFormat: "raw",
79
+ payloadFormat: "raw",
80
80
  customConfig: {
81
- encoding: 'base64',
82
- secretEncoding: 'utf8',
81
+ encoding: "base64",
82
+ secretEncoding: "utf8",
83
83
  },
84
84
  },
85
- description: 'Shopify webhooks use HMAC-SHA256 with base64 encoded signature',
85
+ description: "Shopify webhooks use HMAC-SHA256 with base64 encoded signature",
86
86
  },
87
87
  vercel: {
88
- platform: 'vercel',
88
+ platform: "vercel",
89
89
  signatureConfig: {
90
- algorithm: 'hmac-sha256',
91
- headerName: 'x-vercel-signature',
92
- headerFormat: 'raw',
93
- timestampHeader: 'x-vercel-timestamp',
94
- timestampFormat: 'unix',
95
- payloadFormat: 'raw',
90
+ algorithm: "hmac-sha256",
91
+ headerName: "x-vercel-signature",
92
+ headerFormat: "raw",
93
+ timestampHeader: "x-vercel-timestamp",
94
+ timestampFormat: "unix",
95
+ payloadFormat: "raw",
96
96
  },
97
- description: 'Vercel webhooks use HMAC-SHA256',
97
+ description: "Vercel webhooks use HMAC-SHA256",
98
98
  },
99
99
  polar: {
100
- platform: 'polar',
100
+ platform: "polar",
101
101
  signatureConfig: {
102
- algorithm: 'hmac-sha256',
103
- headerName: 'webhook-signature',
104
- headerFormat: 'raw',
105
- timestampHeader: 'webhook-timestamp',
106
- timestampFormat: 'unix',
107
- payloadFormat: 'custom',
102
+ algorithm: "hmac-sha256",
103
+ headerName: "webhook-signature",
104
+ headerFormat: "raw",
105
+ timestampHeader: "webhook-timestamp",
106
+ timestampFormat: "unix",
107
+ payloadFormat: "custom",
108
108
  customConfig: {
109
- signatureFormat: 'v1={signature}',
110
- payloadFormat: '{id}.{timestamp}.{body}',
111
- encoding: 'base64',
112
- secretEncoding: 'utf8',
113
- idHeader: 'webhook-id',
109
+ signatureFormat: "v1={signature}",
110
+ payloadFormat: "{id}.{timestamp}.{body}",
111
+ encoding: "base64",
112
+ secretEncoding: "utf8",
113
+ idHeader: "webhook-id",
114
114
  },
115
115
  },
116
- description: 'Polar webhooks use HMAC-SHA256 with Standard Webhooks format',
116
+ description: "Polar webhooks use HMAC-SHA256 with Standard Webhooks format",
117
117
  },
118
118
  gitlab: {
119
- platform: 'gitlab',
119
+ platform: "gitlab",
120
120
  signatureConfig: {
121
- algorithm: 'custom',
122
- headerName: 'X-Gitlab-Token',
123
- headerFormat: 'raw',
124
- payloadFormat: 'raw',
121
+ algorithm: "custom",
122
+ headerName: "X-Gitlab-Token",
123
+ headerFormat: "raw",
124
+ payloadFormat: "raw",
125
125
  customConfig: {
126
- type: 'token-based',
127
- idHeader: 'X-Gitlab-Token',
126
+ type: "token-based",
127
+ idHeader: "X-Gitlab-Token",
128
128
  },
129
129
  },
130
- description: 'GitLab webhooks use token-based authentication via X-Gitlab-Token header',
130
+ description: "GitLab webhooks use token-based authentication via X-Gitlab-Token header",
131
131
  },
132
132
  paddle: {
133
- platform: 'paddle',
133
+ platform: "paddle",
134
134
  signatureConfig: {
135
- algorithm: 'hmac-sha256',
136
- headerName: 'paddle-signature',
137
- headerFormat: 'comma-separated',
138
- payloadFormat: 'custom',
135
+ algorithm: "hmac-sha256",
136
+ headerName: "paddle-signature",
137
+ headerFormat: "comma-separated",
138
+ payloadFormat: "custom",
139
139
  customConfig: {
140
- timestampKey: 'ts',
141
- signatureKey: 'h1',
142
- payloadFormat: '{timestamp}:{body}',
140
+ timestampKey: "ts",
141
+ signatureKey: "h1",
142
+ payloadFormat: "{timestamp}:{body}",
143
143
  },
144
144
  },
145
- description: 'Paddle webhooks use HMAC-SHA256 with Paddle-Signature (ts/h1) header format',
145
+ description: "Paddle webhooks use HMAC-SHA256 with Paddle-Signature (ts/h1) header format",
146
146
  },
147
147
  razorpay: {
148
- platform: 'razorpay',
148
+ platform: "razorpay",
149
149
  signatureConfig: {
150
- algorithm: 'hmac-sha256',
151
- headerName: 'x-razorpay-signature',
152
- headerFormat: 'raw',
153
- payloadFormat: 'raw',
150
+ algorithm: "hmac-sha256",
151
+ headerName: "x-razorpay-signature",
152
+ headerFormat: "raw",
153
+ payloadFormat: "raw",
154
154
  },
155
- description: 'Razorpay webhooks use HMAC-SHA256 with X-Razorpay-Signature header',
155
+ description: "Razorpay webhooks use HMAC-SHA256 with X-Razorpay-Signature header",
156
156
  },
157
157
  lemonsqueezy: {
158
- platform: 'lemonsqueezy',
158
+ platform: "lemonsqueezy",
159
159
  signatureConfig: {
160
- algorithm: 'hmac-sha256',
161
- headerName: 'x-signature',
162
- headerFormat: 'raw',
163
- payloadFormat: 'raw',
160
+ algorithm: "hmac-sha256",
161
+ headerName: "x-signature",
162
+ headerFormat: "raw",
163
+ payloadFormat: "raw",
164
164
  },
165
- description: 'Lemon Squeezy webhooks use HMAC-SHA256 with X-Signature header',
165
+ description: "Lemon Squeezy webhooks use HMAC-SHA256 with X-Signature header",
166
166
  },
167
167
  workos: {
168
- platform: 'workos',
168
+ platform: "workos",
169
169
  signatureConfig: {
170
- algorithm: 'hmac-sha256',
171
- headerName: 'workos-signature',
172
- headerFormat: 'comma-separated',
173
- payloadFormat: 'custom',
170
+ algorithm: "hmac-sha256",
171
+ headerName: "workos-signature",
172
+ headerFormat: "comma-separated",
173
+ payloadFormat: "custom",
174
174
  customConfig: {
175
- timestampKey: 't',
176
- signatureKey: 'v1',
177
- payloadFormat: '{timestamp}.{body}',
175
+ timestampKey: "t",
176
+ signatureKey: "v1",
177
+ payloadFormat: "{timestamp}.{body}",
178
178
  },
179
179
  },
180
- description: 'WorkOS webhooks use HMAC-SHA256 with WorkOS-Signature (t/v1) format',
180
+ description: "WorkOS webhooks use HMAC-SHA256 with WorkOS-Signature (t/v1) format",
181
181
  },
182
182
  woocommerce: {
183
- platform: 'woocommerce',
183
+ platform: "woocommerce",
184
184
  signatureConfig: {
185
- algorithm: 'hmac-sha256',
186
- headerName: 'x-wc-webhook-signature',
187
- headerFormat: 'raw',
188
- payloadFormat: 'raw',
185
+ algorithm: "hmac-sha256",
186
+ headerName: "x-wc-webhook-signature",
187
+ headerFormat: "raw",
188
+ payloadFormat: "raw",
189
189
  customConfig: {
190
- encoding: 'base64',
191
- secretEncoding: 'utf8',
190
+ encoding: "base64",
191
+ secretEncoding: "utf8",
192
192
  },
193
193
  },
194
- description: 'WooCommerce webhooks use HMAC-SHA256 with base64 encoded signature',
194
+ description: "WooCommerce webhooks use HMAC-SHA256 with base64 encoded signature",
195
195
  },
196
196
  replicateai: {
197
- platform: 'replicateai',
197
+ platform: "replicateai",
198
198
  signatureConfig: {
199
- algorithm: 'hmac-sha256',
200
- headerName: 'webhook-signature',
201
- headerFormat: 'raw',
202
- timestampHeader: 'webhook-timestamp',
203
- timestampFormat: 'unix',
204
- payloadFormat: 'custom',
199
+ algorithm: "hmac-sha256",
200
+ headerName: "webhook-signature",
201
+ headerFormat: "raw",
202
+ timestampHeader: "webhook-timestamp",
203
+ timestampFormat: "unix",
204
+ payloadFormat: "custom",
205
205
  customConfig: {
206
- signatureFormat: 'v1={signature}',
207
- payloadFormat: '{id}.{timestamp}.{body}',
208
- encoding: 'base64',
209
- secretEncoding: 'base64',
210
- idHeader: 'webhook-id',
206
+ signatureFormat: "v1={signature}",
207
+ payloadFormat: "{id}.{timestamp}.{body}",
208
+ encoding: "base64",
209
+ secretEncoding: "base64",
210
+ idHeader: "webhook-id",
211
211
  },
212
212
  },
213
- description: 'Replicate webhooks use HMAC-SHA256 with Standard Webhooks (svix-style) format',
213
+ description: "Replicate webhooks use HMAC-SHA256 with Standard Webhooks (svix-style) format",
214
214
  },
215
215
  falai: {
216
- platform: 'falai',
216
+ platform: "falai",
217
217
  signatureConfig: {
218
- algorithm: 'ed25519',
219
- headerName: 'x-fal-webhook-signature',
220
- headerFormat: 'raw',
221
- payloadFormat: 'custom',
218
+ algorithm: "ed25519",
219
+ headerName: "x-fal-webhook-signature",
220
+ headerFormat: "raw",
221
+ payloadFormat: "custom",
222
222
  customConfig: {
223
- requestIdHeader: 'x-fal-webhook-request-id',
224
- userIdHeader: 'x-fal-webhook-user-id',
225
- timestampHeader: 'x-fal-webhook-timestamp',
226
- jwksUrl: 'https://rest.alpha.fal.ai/.well-known/jwks.json',
223
+ requestIdHeader: "x-fal-webhook-request-id",
224
+ userIdHeader: "x-fal-webhook-user-id",
225
+ timestampHeader: "x-fal-webhook-timestamp",
226
+ jwksUrl: "https://rest.alpha.fal.ai/.well-known/jwks.json",
227
227
  },
228
228
  },
229
- description: 'fal.ai webhooks use ED25519 with JWKS key verification. No secret required — pass empty string.',
229
+ description: "fal.ai webhooks use ED25519 with JWKS key verification. No secret required — pass empty string.",
230
230
  },
231
231
  sentry: {
232
- platform: 'sentry',
232
+ platform: "sentry",
233
233
  signatureConfig: {
234
- algorithm: 'hmac-sha256',
235
- headerName: 'sentry-hook-signature',
236
- headerFormat: 'raw',
237
- timestampHeader: 'sentry-hook-timestamp',
238
- timestampFormat: 'unix',
239
- payloadFormat: 'json-stringified',
240
- idHeader: 'request-id',
234
+ algorithm: "hmac-sha256",
235
+ headerName: "sentry-hook-signature",
236
+ headerFormat: "raw",
237
+ timestampHeader: "sentry-hook-timestamp",
238
+ timestampFormat: "unix",
239
+ payloadFormat: "json-stringified",
240
+ idHeader: "request-id",
241
241
  customConfig: {
242
- issueAlertPayloadPath: 'data.issue_alert',
242
+ issueAlertPayloadPath: "data.issue_alert",
243
243
  },
244
244
  },
245
- description: 'Sentry webhooks use HMAC-SHA256 with JSON stringified body and Request-ID idempotency key',
245
+ description: "Sentry webhooks use HMAC-SHA256 with JSON stringified body and Request-ID idempotency key",
246
246
  },
247
247
  grafana: {
248
- platform: 'grafana',
248
+ platform: "grafana",
249
249
  signatureConfig: {
250
- algorithm: 'hmac-sha256',
251
- headerName: 'x-grafana-alerting-signature',
252
- headerFormat: 'raw',
253
- timestampHeader: 'x-grafana-alerting-timestamp',
254
- timestampFormat: 'unix',
255
- payloadFormat: 'timestamped',
250
+ algorithm: "hmac-sha256",
251
+ headerName: "x-grafana-alerting-signature",
252
+ headerFormat: "raw",
253
+ timestampHeader: "x-grafana-alerting-timestamp",
254
+ timestampFormat: "unix",
255
+ payloadFormat: "timestamped",
256
256
  },
257
- description: 'Grafana 12+ webhooks support HMAC-SHA256 with optional timestamped payload format',
257
+ description: "Grafana 12+ webhooks support HMAC-SHA256 with optional timestamped payload format",
258
258
  },
259
259
  doppler: {
260
- platform: 'doppler',
260
+ platform: "doppler",
261
261
  signatureConfig: {
262
- algorithm: 'hmac-sha256',
263
- headerName: 'x-doppler-signature',
264
- headerFormat: 'prefixed',
265
- prefix: 'sha256=',
266
- payloadFormat: 'raw',
262
+ algorithm: "hmac-sha256",
263
+ headerName: "x-doppler-signature",
264
+ headerFormat: "prefixed",
265
+ prefix: "sha256=",
266
+ payloadFormat: "raw",
267
267
  customConfig: {
268
- dedupHashAlgorithm: 'sha256',
268
+ dedupHashAlgorithm: "sha256",
269
269
  },
270
270
  },
271
- description: 'Doppler webhooks use HMAC-SHA256 with sha256= signature prefix and raw payload signing',
271
+ description: "Doppler webhooks use HMAC-SHA256 with sha256= signature prefix and raw payload signing",
272
272
  },
273
273
  sanity: {
274
- platform: 'sanity',
274
+ platform: "sanity",
275
275
  signatureConfig: {
276
- algorithm: 'hmac-sha256',
277
- headerName: 'sanity-webhook-signature',
278
- headerFormat: 'comma-separated',
279
- payloadFormat: 'timestamped',
276
+ algorithm: "hmac-sha256",
277
+ headerName: "sanity-webhook-signature",
278
+ headerFormat: "comma-separated",
279
+ payloadFormat: "timestamped",
280
280
  customConfig: {
281
- timestampKey: 't',
282
- signatureKey: 'v1',
281
+ timestampKey: "t",
282
+ signatureKey: "v1",
283
+ encoding: "base64",
284
+ secretEncoding: "utf8",
283
285
  },
284
- idHeader: 'idempotency-key',
286
+ idHeader: "idempotency-key",
285
287
  },
286
- description: 'Sanity webhooks use Stripe-compatible signatures with timestamp/body payload and idempotency key header',
288
+ description: "Sanity webhooks use Stripe-compatible HMAC-SHA256 with base64 encoded signature and plain UTF-8 secret",
287
289
  },
288
290
  custom: {
289
- platform: 'custom',
291
+ platform: "custom",
290
292
  signatureConfig: {
291
- algorithm: 'hmac-sha256',
292
- headerName: 'x-webhook-signature',
293
- headerFormat: 'raw',
294
- payloadFormat: 'raw',
293
+ algorithm: "hmac-sha256",
294
+ headerName: "x-webhook-signature",
295
+ headerFormat: "raw",
296
+ payloadFormat: "raw",
295
297
  customConfig: {
296
- type: 'token-based',
297
- idHeader: 'x-webhook-id',
298
+ type: "token-based",
299
+ idHeader: "x-webhook-id",
298
300
  },
299
301
  },
300
- description: 'Custom webhook configuration (supports token-based overrides via customConfig)',
302
+ description: "Custom webhook configuration (supports token-based overrides via customConfig)",
301
303
  },
302
304
  unknown: {
303
- platform: 'unknown',
305
+ platform: "unknown",
304
306
  signatureConfig: {
305
- algorithm: 'hmac-sha256',
306
- headerName: 'x-webhook-signature',
307
- headerFormat: 'raw',
308
- payloadFormat: 'raw',
307
+ algorithm: "hmac-sha256",
308
+ headerName: "x-webhook-signature",
309
+ headerFormat: "raw",
310
+ payloadFormat: "raw",
309
311
  },
310
- description: 'Unknown platform - using default HMAC-SHA256',
312
+ description: "Unknown platform - using default HMAC-SHA256",
311
313
  },
312
314
  };
313
315
  function getPlatformAlgorithmConfig(platform) {
@@ -327,15 +329,15 @@ function validateSignatureConfig(config) {
327
329
  return false;
328
330
  }
329
331
  switch (config.algorithm) {
330
- case 'hmac-sha256':
331
- case 'hmac-sha1':
332
- case 'hmac-sha512':
332
+ case "hmac-sha256":
333
+ case "hmac-sha1":
334
+ case "hmac-sha512":
333
335
  return true;
334
- case 'rsa-sha256':
336
+ case "rsa-sha256":
335
337
  return !!config.customConfig?.publicKey;
336
- case 'ed25519':
338
+ case "ed25519":
337
339
  return !!config.customConfig?.publicKey || !!config.customConfig?.jwksUrl;
338
- case 'custom':
340
+ case "custom":
339
341
  return !!config.customConfig;
340
342
  default:
341
343
  return false;
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,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveQueueConfig = resolveQueueConfig;
4
+ exports.handleReceive = handleReceive;
5
+ exports.handleProcess = handleProcess;
6
+ exports.handleQueuedRequest = handleQueuedRequest;
7
+ const index_1 = require("../index");
8
+ function loadQStashModule() {
9
+ try {
10
+ return require('@upstash/qstash');
11
+ }
12
+ catch {
13
+ throw new Error('[tern] Queue support requires optional peer dependency "@upstash/qstash". Please install it to use queue mode.');
14
+ }
15
+ }
16
+ function nonRetryableResponse(message, status = 489) {
17
+ return new Response(JSON.stringify({ error: message }), {
18
+ status,
19
+ headers: {
20
+ 'Content-Type': 'application/json',
21
+ 'Upstash-NonRetryable-Error': 'true',
22
+ },
23
+ });
24
+ }
25
+ function resolveQueueConfig(queue) {
26
+ if (queue === true) {
27
+ const token = process.env.QSTASH_TOKEN;
28
+ const signingKey = process.env.QSTASH_CURRENT_SIGNING_KEY;
29
+ const nextSigningKey = process.env.QSTASH_NEXT_SIGNING_KEY;
30
+ if (!token || !signingKey || !nextSigningKey) {
31
+ throw new Error('[tern] queue: true requires QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY in env');
32
+ }
33
+ return {
34
+ token,
35
+ signingKey,
36
+ nextSigningKey,
37
+ };
38
+ }
39
+ return queue;
40
+ }
41
+ async function handleReceive(request, platform, secret, queueConfig, toleranceInSeconds) {
42
+ const verificationResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request.clone(), platform, secret, toleranceInSeconds);
43
+ if (!verificationResult.isValid) {
44
+ return nonRetryableResponse(verificationResult.error || 'Webhook signature verification failed');
45
+ }
46
+ const { Client } = loadQStashModule();
47
+ const client = new Client({ token: queueConfig.token });
48
+ const queuedMessage = {
49
+ platform,
50
+ payload: verificationResult.payload,
51
+ metadata: verificationResult.metadata || {},
52
+ };
53
+ const idempotencyKey = request.headers.get('idempotency-key')
54
+ || request.headers.get('x-webhook-id')
55
+ || undefined;
56
+ const publishPayload = {
57
+ url: request.url,
58
+ body: queuedMessage,
59
+ deduplicationId: idempotencyKey,
60
+ };
61
+ if (queueConfig.retries !== undefined) {
62
+ publishPayload.retries = queueConfig.retries;
63
+ }
64
+ await client.publishJSON(publishPayload);
65
+ return new Response(JSON.stringify({ queued: true }), {
66
+ status: 200,
67
+ headers: {
68
+ 'Content-Type': 'application/json',
69
+ },
70
+ });
71
+ }
72
+ async function handleProcess(request, handler, queueConfig) {
73
+ const signature = request.headers.get('upstash-signature');
74
+ if (!signature) {
75
+ return new Response(JSON.stringify({ error: 'Missing Upstash-Signature header' }), {
76
+ status: 401,
77
+ headers: { 'Content-Type': 'application/json' },
78
+ });
79
+ }
80
+ const rawBody = await request.text();
81
+ const { Receiver } = loadQStashModule();
82
+ const receiver = new Receiver({
83
+ currentSigningKey: queueConfig.signingKey,
84
+ nextSigningKey: queueConfig.nextSigningKey,
85
+ });
86
+ try {
87
+ const verification = await receiver.verify({
88
+ signature,
89
+ body: rawBody,
90
+ url: request.url,
91
+ });
92
+ if (verification === false) {
93
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), {
94
+ status: 401,
95
+ headers: { 'Content-Type': 'application/json' },
96
+ });
97
+ }
98
+ }
99
+ catch {
100
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), {
101
+ status: 401,
102
+ headers: { 'Content-Type': 'application/json' },
103
+ });
104
+ }
105
+ if (!handler) {
106
+ return nonRetryableResponse('Queue processing requires a handler function');
107
+ }
108
+ let parsedBody;
109
+ try {
110
+ parsedBody = JSON.parse(rawBody);
111
+ }
112
+ catch {
113
+ return nonRetryableResponse('Queued payload is not valid JSON');
114
+ }
115
+ try {
116
+ await handler(parsedBody.payload, parsedBody.metadata || {});
117
+ return new Response(JSON.stringify({ delivered: true }), {
118
+ status: 200,
119
+ headers: {
120
+ 'Content-Type': 'application/json',
121
+ },
122
+ });
123
+ }
124
+ catch (error) {
125
+ return new Response(JSON.stringify({ error: error.message || 'Handler execution failed' }), {
126
+ status: 500,
127
+ headers: {
128
+ 'Content-Type': 'application/json',
129
+ },
130
+ });
131
+ }
132
+ }
133
+ async function handleQueuedRequest(request, options) {
134
+ if (request.headers.has('upstash-signature')) {
135
+ return handleProcess(request, options.handler, options.queueConfig);
136
+ }
137
+ return handleReceive(request, options.platform, options.secret, options.queueConfig, options.toleranceInSeconds);
138
+ }
@@ -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.3",
3
+ "version": "3.0.5-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",
@@ -96,7 +96,13 @@
96
96
  "import": "./dist/cloudflare.js",
97
97
  "default": "./dist/cloudflare.js"
98
98
  },
99
- "./package.json": "./package.json"
99
+ "./package.json": "./package.json",
100
+ "./upstash": {
101
+ "types": "./dist/upstash/index.d.ts",
102
+ "require": "./dist/upstash/index.js",
103
+ "import": "./dist/upstash/index.js",
104
+ "default": "./dist/upstash/index.js"
105
+ }
100
106
  },
101
107
  "typesVersions": {
102
108
  "*": {
@@ -111,7 +117,18 @@
111
117
  ],
112
118
  "*": [
113
119
  "dist/index.d.ts"
120
+ ],
121
+ "upstash": [
122
+ "dist/upstash/index.d.ts"
114
123
  ]
115
124
  }
125
+ },
126
+ "peerDependencies": {
127
+ "@upstash/qstash": ">=2.0.0"
128
+ },
129
+ "peerDependenciesMeta": {
130
+ "@upstash/qstash": {
131
+ "optional": true
132
+ }
116
133
  }
117
134
  }