@hookflo/tern 4.3.0-beta.0 → 4.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +31 -0
  2. package/dist/adapters/cloudflare.d.ts +2 -2
  3. package/dist/adapters/cloudflare.js +6 -1
  4. package/dist/adapters/express.d.ts +2 -2
  5. package/dist/adapters/express.js +6 -1
  6. package/dist/adapters/hono.d.ts +2 -2
  7. package/dist/adapters/hono.js +6 -1
  8. package/dist/adapters/nextjs.d.ts +2 -2
  9. package/dist/adapters/nextjs.js +6 -1
  10. package/dist/adapters/shared.js +5 -2
  11. package/dist/index.d.ts +3 -4
  12. package/dist/index.js +29 -19
  13. package/dist/platforms/algorithms.js +65 -0
  14. package/dist/types.d.ts +6 -65
  15. package/dist/types.js +4 -0
  16. package/dist/verifiers/algorithms.d.ts +8 -0
  17. package/dist/verifiers/algorithms.js +145 -17
  18. package/package.json +1 -1
  19. package/dist/normalization/index.d.ts +0 -20
  20. package/dist/normalization/index.js +0 -78
  21. package/dist/normalization/providers/payment/paypal.d.ts +0 -2
  22. package/dist/normalization/providers/payment/paypal.js +0 -12
  23. package/dist/normalization/providers/payment/razorpay.d.ts +0 -2
  24. package/dist/normalization/providers/payment/razorpay.js +0 -13
  25. package/dist/normalization/providers/payment/stripe.d.ts +0 -2
  26. package/dist/normalization/providers/payment/stripe.js +0 -13
  27. package/dist/normalization/providers/registry.d.ts +0 -5
  28. package/dist/normalization/providers/registry.js +0 -21
  29. package/dist/normalization/simple.d.ts +0 -4
  30. package/dist/normalization/simple.js +0 -126
  31. package/dist/normalization/storage/interface.d.ts +0 -13
  32. package/dist/normalization/storage/interface.js +0 -2
  33. package/dist/normalization/storage/memory.d.ts +0 -12
  34. package/dist/normalization/storage/memory.js +0 -39
  35. package/dist/normalization/templates/base/auth.d.ts +0 -2
  36. package/dist/normalization/templates/base/auth.js +0 -22
  37. package/dist/normalization/templates/base/ecommerce.d.ts +0 -2
  38. package/dist/normalization/templates/base/ecommerce.js +0 -25
  39. package/dist/normalization/templates/base/payment.d.ts +0 -2
  40. package/dist/normalization/templates/base/payment.js +0 -25
  41. package/dist/normalization/templates/registry.d.ts +0 -6
  42. package/dist/normalization/templates/registry.js +0 -22
  43. package/dist/normalization/transformer/engine.d.ts +0 -11
  44. package/dist/normalization/transformer/engine.js +0 -86
  45. package/dist/normalization/transformer/validator.d.ts +0 -12
  46. package/dist/normalization/transformer/validator.js +0 -56
  47. package/dist/normalization/types.d.ts +0 -79
  48. package/dist/normalization/types.js +0 -2
@@ -13,6 +13,40 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
13
13
  this.config = config;
14
14
  this.platform = platform;
15
15
  }
16
+ getMissingSignatureMessage() {
17
+ return `Missing signature header: ${this.config.headerName}. Ensure your webhook provider sends this header and your adapter forwards it unchanged.`;
18
+ }
19
+ getMissingTimestampMessage() {
20
+ const timestampHeader = this.config.timestampHeader || this.config.customConfig?.timestampHeader || 'timestamp';
21
+ return `Missing required timestamp for webhook verification. Verify header '${timestampHeader}' is present and passed through by your framework/proxy.`;
22
+ }
23
+ getTimestampExpiredMessage() {
24
+ return 'Webhook timestamp expired. Check server clock drift and increase tolerance only if your provider allows it.';
25
+ }
26
+ getInvalidSignatureMessage() {
27
+ const genericHint = `Invalid signature for ${this.platform}. Confirm webhook secret, raw request body handling, and signature header formatting.`;
28
+ switch (this.platform) {
29
+ case 'twilio':
30
+ return `${genericHint} Twilio also requires the exact public URL used for signing (including query params like bodySHA256). Use twilioBaseUrl if your runtime URL is rewritten behind a proxy.`;
31
+ case 'stripe':
32
+ return `${genericHint} Stripe signatures require the exact raw body and Stripe-Signature timestamp/value pair.`;
33
+ case 'github':
34
+ return `${genericHint} GitHub signatures must include the sha256= prefix from x-hub-signature-256.`;
35
+ case 'svix':
36
+ case 'clerk':
37
+ case 'dodopayments':
38
+ case 'replicateai':
39
+ case 'polar':
40
+ return `${genericHint} Standard Webhooks payload must be signed as id.timestamp.body and secrets may need whsec_ base64 decoding.`;
41
+ case 'pagerduty':
42
+ return `${genericHint} PagerDuty expects v1=<hex> signature values from x-pagerduty-signature.`;
43
+ default:
44
+ return genericHint;
45
+ }
46
+ }
47
+ getVerificationErrorMessage(error) {
48
+ return `${this.platform} verification error: ${error.message}. Check webhook secret configuration and ensure your framework preserves raw body + headers.`;
49
+ }
16
50
  parseDelimitedHeader(headerValue) {
17
51
  const parts = headerValue.split(/[;,]/);
18
52
  const values = {};
@@ -63,9 +97,34 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
63
97
  }
64
98
  // Accept "v1=<signature>" variants used by some providers/docs.
65
99
  if (sig.startsWith("v1=")) {
66
- const [, value] = sig.split("=", 2);
67
- if (value) {
68
- normalized.push(value.trim());
100
+ if (this.config.customConfig?.comparePrefixed) {
101
+ for (const fragment of sig.split(',')) {
102
+ const candidate = fragment.trim();
103
+ if (candidate.startsWith('v1=')) {
104
+ normalized.push(candidate);
105
+ }
106
+ }
107
+ }
108
+ else {
109
+ const [, value] = sig.split("=", 2);
110
+ if (value) {
111
+ normalized.push(value.trim());
112
+ }
113
+ }
114
+ continue;
115
+ }
116
+ for (const fragment of sig.split(',')) {
117
+ const candidate = fragment.trim();
118
+ if (candidate.startsWith('v1=')) {
119
+ if (this.config.customConfig?.comparePrefixed) {
120
+ normalized.push(candidate);
121
+ }
122
+ else {
123
+ const [, value] = candidate.split('=', 2);
124
+ if (value) {
125
+ normalized.push(value.trim());
126
+ }
127
+ }
69
128
  }
70
129
  }
71
130
  }
@@ -77,7 +136,9 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
77
136
  extractTimestamp(request) {
78
137
  if (!this.config.timestampHeader)
79
138
  return null;
80
- const timestampHeader = request.headers.get(this.config.timestampHeader);
139
+ const timestampHeader = request.headers.get(this.config.timestampHeader)
140
+ || this.config.customConfig?.timestampHeaderAliases?.map((alias) => request.headers.get(alias)).find(Boolean)
141
+ || null;
81
142
  if (!timestampHeader)
82
143
  return null;
83
144
  switch (this.config.timestampFormat) {
@@ -108,7 +169,7 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
108
169
  return true;
109
170
  // These platforms have timestampHeader in config but timestamp
110
171
  // is optional in their spec — validate only if present, never mandate
111
- const optionalTimestampPlatforms = ['vercel', 'sentry', 'grafana'];
172
+ const optionalTimestampPlatforms = ['vercel', 'sentry', 'grafana', 'twilio'];
112
173
  if (optionalTimestampPlatforms.includes(this.platform))
113
174
  return false;
114
175
  // For all other platforms: infer from config
@@ -125,6 +186,16 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
125
186
  return true;
126
187
  return false;
127
188
  }
189
+ resolveTwilioSignatureUrl(request) {
190
+ const overrideBaseUrl = this.config.customConfig?.twilioBaseUrl;
191
+ if (!overrideBaseUrl) {
192
+ return request.url;
193
+ }
194
+ const requestUrl = new URL(request.url);
195
+ const baseUrl = new URL(overrideBaseUrl);
196
+ baseUrl.search = requestUrl.search;
197
+ return baseUrl.toString();
198
+ }
128
199
  formatPayload(rawBody, request) {
129
200
  switch (this.config.payloadFormat) {
130
201
  case "timestamped": {
@@ -152,7 +223,7 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
152
223
  }
153
224
  const customFormat = this.config.customConfig.payloadFormat;
154
225
  if (customFormat.includes("{id}") && customFormat.includes("{timestamp}")) {
155
- const id = request.headers.get(this.config.customConfig.idHeader || "x-webhook-id");
226
+ const id = request.headers.get(this.config.customConfig.idHeader || "x-webhook-id") || this.config.customConfig?.idHeaderAliases?.map((alias) => request.headers.get(alias)).find(Boolean);
156
227
  const timestamp = request.headers.get(this.config.timestampHeader ||
157
228
  this.config.customConfig?.timestampHeader ||
158
229
  "x-webhook-timestamp");
@@ -167,6 +238,11 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
167
238
  .replace("{timestamp}", timestamp.trim() || "")
168
239
  .replace("{body}", rawBody);
169
240
  }
241
+ if (customFormat.includes('{url}')) {
242
+ return customFormat
243
+ .replace('{url}', this.platform === 'twilio' ? this.resolveTwilioSignatureUrl(request) : request.url)
244
+ .replace('{body}', rawBody);
245
+ }
170
246
  if (customFormat.includes("{timestamp}") &&
171
247
  customFormat.includes("{body}")) {
172
248
  const timestamp = this.extractTimestampFromSignature(request) ||
@@ -250,6 +326,40 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
250
326
  }
251
327
  exports.AlgorithmBasedVerifier = AlgorithmBasedVerifier;
252
328
  class GenericHMACVerifier extends AlgorithmBasedVerifier {
329
+ validateLinearReplayWindow(rawBody) {
330
+ if (this.platform !== 'linear')
331
+ return null;
332
+ try {
333
+ const parsed = JSON.parse(rawBody);
334
+ const rawTimestamp = parsed.webhookTimestamp;
335
+ const timestampMs = Number(rawTimestamp);
336
+ if (!Number.isFinite(timestampMs)) {
337
+ return 'Missing or invalid Linear webhookTimestamp';
338
+ }
339
+ const replayToleranceMs = this.config.customConfig?.replayToleranceMs || 60000;
340
+ if (Math.abs(Date.now() - timestampMs) > replayToleranceMs) {
341
+ return 'Linear webhook timestamp is outside the replay window';
342
+ }
343
+ }
344
+ catch {
345
+ return 'Linear webhook replay check requires JSON payload';
346
+ }
347
+ return null;
348
+ }
349
+ validateTwilioBodyHash(rawBody, request) {
350
+ if (this.platform !== 'twilio' || !this.config.customConfig?.validateBodySHA256) {
351
+ return null;
352
+ }
353
+ const url = new URL(this.resolveTwilioSignatureUrl(request));
354
+ const bodySha = url.searchParams.get('bodySHA256');
355
+ if (!bodySha)
356
+ return null;
357
+ const computed = (0, crypto_1.createHash)('sha256').update(rawBody).digest('hex');
358
+ if (!this.safeCompare(computed, bodySha)) {
359
+ return 'Twilio bodySHA256 query param does not match payload hash';
360
+ }
361
+ return null;
362
+ }
253
363
  resolveSentryPayloadCandidates(rawBody, request) {
254
364
  const candidates = [
255
365
  this.formatPayload(rawBody, request),
@@ -283,12 +393,30 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
283
393
  if (signatures.length === 0) {
284
394
  return {
285
395
  isValid: false,
286
- error: `Missing signature header: ${this.config.headerName}`,
396
+ error: this.getMissingSignatureMessage(),
287
397
  errorCode: "MISSING_SIGNATURE",
288
398
  platform: this.platform,
289
399
  };
290
400
  }
291
401
  const rawBody = await request.text();
402
+ const linearReplayError = this.validateLinearReplayWindow(rawBody);
403
+ if (linearReplayError) {
404
+ return {
405
+ isValid: false,
406
+ error: linearReplayError,
407
+ errorCode: 'TIMESTAMP_EXPIRED',
408
+ platform: this.platform,
409
+ };
410
+ }
411
+ const twilioBodyHashError = this.validateTwilioBodyHash(rawBody, request);
412
+ if (twilioBodyHashError) {
413
+ return {
414
+ isValid: false,
415
+ error: twilioBodyHashError,
416
+ errorCode: 'INVALID_SIGNATURE',
417
+ platform: this.platform,
418
+ };
419
+ }
292
420
  let timestamp = null;
293
421
  if (this.config.headerFormat === "comma-separated") {
294
422
  timestamp = this.extractTimestampFromSignature(request);
@@ -299,7 +427,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
299
427
  if (this.requiresTimestamp() && !timestamp) {
300
428
  return {
301
429
  isValid: false,
302
- error: 'Missing required timestamp for webhook verification',
430
+ error: this.getMissingTimestampMessage(),
303
431
  errorCode: 'MISSING_SIGNATURE',
304
432
  platform: this.platform,
305
433
  };
@@ -307,7 +435,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
307
435
  if (timestamp && !this.isTimestampValid(timestamp)) {
308
436
  return {
309
437
  isValid: false,
310
- error: "Webhook timestamp expired",
438
+ error: this.getTimestampExpiredMessage(),
311
439
  errorCode: "TIMESTAMP_EXPIRED",
312
440
  platform: this.platform,
313
441
  };
@@ -322,7 +450,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
322
450
  if (this.config.customConfig?.encoding === "base64") {
323
451
  isValid = this.verifyHMACWithBase64(payload, signature, algorithm);
324
452
  }
325
- else if (this.config.headerFormat === "prefixed") {
453
+ else if (this.config.headerFormat === "prefixed" || this.config.customConfig?.comparePrefixed) {
326
454
  isValid = this.verifyHMACWithPrefix(payload, signature, algorithm);
327
455
  }
328
456
  else {
@@ -339,7 +467,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
339
467
  if (!isValid) {
340
468
  return {
341
469
  isValid: false,
342
- error: "Invalid signature",
470
+ error: this.getInvalidSignatureMessage(),
343
471
  errorCode: "INVALID_SIGNATURE",
344
472
  platform: this.platform,
345
473
  };
@@ -368,7 +496,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
368
496
  catch (error) {
369
497
  return {
370
498
  isValid: false,
371
- error: `${this.platform} verification error: ${error.message}`,
499
+ error: this.getVerificationErrorMessage(error),
372
500
  errorCode: "VERIFICATION_ERROR",
373
501
  platform: this.platform,
374
502
  };
@@ -477,7 +605,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
477
605
  if (signatures.length === 0) {
478
606
  return {
479
607
  isValid: false,
480
- error: `Missing signature header: ${this.config.headerName}`,
608
+ error: this.getMissingSignatureMessage(),
481
609
  errorCode: "MISSING_SIGNATURE",
482
610
  platform: this.platform,
483
611
  };
@@ -491,7 +619,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
491
619
  if (!timestampStr) {
492
620
  return {
493
621
  isValid: false,
494
- error: 'Missing required timestamp for webhook verification',
622
+ error: this.getMissingTimestampMessage(),
495
623
  errorCode: 'MISSING_SIGNATURE',
496
624
  platform: this.platform,
497
625
  };
@@ -500,7 +628,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
500
628
  if (!this.isTimestampValid(timestamp)) {
501
629
  return {
502
630
  isValid: false,
503
- error: "Webhook timestamp expired",
631
+ error: this.getTimestampExpiredMessage(),
504
632
  errorCode: "TIMESTAMP_EXPIRED",
505
633
  platform: this.platform,
506
634
  };
@@ -560,7 +688,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
560
688
  if (!isValid) {
561
689
  return {
562
690
  isValid: false,
563
- error: "Invalid signature",
691
+ error: this.getInvalidSignatureMessage(),
564
692
  errorCode: "INVALID_SIGNATURE",
565
693
  platform: this.platform,
566
694
  };
@@ -589,7 +717,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
589
717
  catch (error) {
590
718
  return {
591
719
  isValid: false,
592
- error: `${this.platform} verification error: ${error.message}`,
720
+ error: this.getVerificationErrorMessage(error),
593
721
  errorCode: "VERIFICATION_ERROR",
594
722
  platform: this.platform,
595
723
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hookflo/tern",
3
- "version": "4.3.0-beta.0",
3
+ "version": "4.3.0-beta.1",
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",
@@ -1,20 +0,0 @@
1
- import { BaseTemplate, CreateSchemaInput, NormalizedResult, ProviderInfo, TemplateCategory, TransformParams, UpdateSchemaInput, UserSchema } from './types';
2
- import { StorageAdapter } from './storage/interface';
3
- export declare class Normalizer {
4
- private readonly storage;
5
- private engine;
6
- constructor(storage?: StorageAdapter);
7
- getBaseTemplates(): Promise<BaseTemplate[]>;
8
- getProviders(category?: TemplateCategory): Promise<ProviderInfo[]>;
9
- createSchema(input: CreateSchemaInput): Promise<UserSchema>;
10
- updateSchema(schemaId: string, updates: UpdateSchemaInput): Promise<void>;
11
- getSchema(id: string): Promise<UserSchema | null>;
12
- transform(params: TransformParams): Promise<NormalizedResult>;
13
- validateSchema(schema: UserSchema): Promise<{
14
- valid: boolean;
15
- errors: string[];
16
- }>;
17
- }
18
- export * from './types';
19
- export * from './storage/interface';
20
- export { InMemoryStorageAdapter } from './storage/memory';
@@ -1,78 +0,0 @@
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.InMemoryStorageAdapter = exports.Normalizer = void 0;
18
- const registry_1 = require("./providers/registry");
19
- const registry_2 = require("./templates/registry");
20
- const memory_1 = require("./storage/memory");
21
- const engine_1 = require("./transformer/engine");
22
- const validator_1 = require("./transformer/validator");
23
- class Normalizer {
24
- constructor(storage = new memory_1.InMemoryStorageAdapter()) {
25
- this.storage = storage;
26
- this.engine = new engine_1.NormalizationEngine(storage, new validator_1.SchemaValidator());
27
- }
28
- async getBaseTemplates() {
29
- return this.storage.listBaseTemplates();
30
- }
31
- async getProviders(category) {
32
- return registry_1.providerRegistry.list(category);
33
- }
34
- async createSchema(input) {
35
- const schema = {
36
- id: generateId(),
37
- userId: input.userId,
38
- baseTemplateId: input.baseTemplateId,
39
- category: input.category,
40
- fields: input.fields,
41
- providerMappings: input.providerMappings,
42
- createdAt: new Date(),
43
- updatedAt: new Date(),
44
- };
45
- await this.storage.saveSchema(schema);
46
- return schema;
47
- }
48
- async updateSchema(schemaId, updates) {
49
- await this.storage.updateSchema(schemaId, updates);
50
- }
51
- async getSchema(id) {
52
- return this.storage.getSchema(id);
53
- }
54
- async transform(params) {
55
- return this.engine.transform(params);
56
- }
57
- async validateSchema(schema) {
58
- const base = (await this.storage.getBaseTemplate(schema.baseTemplateId))
59
- ?? registry_2.templateRegistry.getById(schema.baseTemplateId);
60
- if (!base) {
61
- return {
62
- valid: false,
63
- errors: [`Base template not found: ${schema.baseTemplateId}`],
64
- };
65
- }
66
- const validator = new validator_1.SchemaValidator();
67
- return validator.validateSchema(schema, base);
68
- }
69
- }
70
- exports.Normalizer = Normalizer;
71
- function generateId() {
72
- // Simple non-crypto unique ID generator for framework default
73
- return (`sch_${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`);
74
- }
75
- __exportStar(require("./types"), exports);
76
- __exportStar(require("./storage/interface"), exports);
77
- var memory_2 = require("./storage/memory");
78
- Object.defineProperty(exports, "InMemoryStorageAdapter", { enumerable: true, get: function () { return memory_2.InMemoryStorageAdapter; } });
@@ -1,2 +0,0 @@
1
- import { ProviderMapping } from '../../types';
2
- export declare const paypalDefaultMapping: ProviderMapping;
@@ -1,12 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.paypalDefaultMapping = void 0;
4
- exports.paypalDefaultMapping = {
5
- provider: 'paypal',
6
- fieldMappings: [
7
- { schemaFieldId: 'event_type', providerPath: 'event_type' },
8
- { schemaFieldId: 'amount', providerPath: 'resource.amount.value', transform: 'toNumber' },
9
- { schemaFieldId: 'currency', providerPath: 'resource.amount.currency_code', transform: 'toUpperCase' },
10
- { schemaFieldId: 'transaction_id', providerPath: 'resource.id' },
11
- ],
12
- };
@@ -1,2 +0,0 @@
1
- import { ProviderMapping } from '../../types';
2
- export declare const razorpayDefaultMapping: ProviderMapping;
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.razorpayDefaultMapping = void 0;
4
- exports.razorpayDefaultMapping = {
5
- provider: 'razorpay',
6
- fieldMappings: [
7
- { schemaFieldId: 'event_type', providerPath: 'event' },
8
- { schemaFieldId: 'amount', providerPath: 'payload.payment.entity.amount' },
9
- { schemaFieldId: 'currency', providerPath: 'payload.payment.entity.currency', transform: 'toUpperCase' },
10
- { schemaFieldId: 'transaction_id', providerPath: 'payload.payment.entity.id' },
11
- { schemaFieldId: 'customer_id', providerPath: 'payload.payment.entity.contact' },
12
- ],
13
- };
@@ -1,2 +0,0 @@
1
- import { ProviderMapping } from '../../types';
2
- export declare const stripeDefaultMapping: ProviderMapping;
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.stripeDefaultMapping = void 0;
4
- exports.stripeDefaultMapping = {
5
- provider: 'stripe',
6
- fieldMappings: [
7
- { schemaFieldId: 'event_type', providerPath: 'type' },
8
- { schemaFieldId: 'amount', providerPath: 'data.object.amount_received' },
9
- { schemaFieldId: 'currency', providerPath: 'data.object.currency', transform: 'toUpperCase' },
10
- { schemaFieldId: 'transaction_id', providerPath: 'data.object.id' },
11
- { schemaFieldId: 'customer_id', providerPath: 'data.object.customer' },
12
- ],
13
- };
@@ -1,5 +0,0 @@
1
- import { ProviderInfo } from '../types';
2
- export declare const providerRegistry: {
3
- list(category?: ProviderInfo["category"]): ProviderInfo[];
4
- getById(id: string): ProviderInfo | undefined;
5
- };
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.providerRegistry = void 0;
4
- const providers = [
5
- { id: 'stripe', name: 'Stripe', category: 'payment' },
6
- { id: 'razorpay', name: 'Razorpay', category: 'payment' },
7
- { id: 'paypal', name: 'PayPal', category: 'payment' },
8
- { id: 'clerk', name: 'Clerk', category: 'auth' },
9
- { id: 'shopify', name: 'Shopify', category: 'ecommerce' },
10
- { id: 'woocommerce', name: 'WooCommerce', category: 'ecommerce' },
11
- ];
12
- exports.providerRegistry = {
13
- list(category) {
14
- if (!category)
15
- return providers;
16
- return providers.filter((p) => p.category === category);
17
- },
18
- getById(id) {
19
- return providers.find((p) => p.id === id);
20
- },
21
- };
@@ -1,4 +0,0 @@
1
- import { AnyNormalizedWebhook, NormalizeOptions, NormalizationCategory, WebhookPlatform } from '../types';
2
- export declare function getPlatformNormalizationCategory(platform: WebhookPlatform): NormalizationCategory | null;
3
- export declare function getPlatformsByCategory(category: NormalizationCategory): WebhookPlatform[];
4
- export declare function normalizePayload(platform: WebhookPlatform, payload: any, normalize?: boolean | NormalizeOptions): AnyNormalizedWebhook | unknown;
@@ -1,126 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getPlatformNormalizationCategory = getPlatformNormalizationCategory;
4
- exports.getPlatformsByCategory = getPlatformsByCategory;
5
- exports.normalizePayload = normalizePayload;
6
- function readPath(payload, path) {
7
- return path.split('.').reduce((acc, key) => {
8
- if (acc === undefined || acc === null) {
9
- return undefined;
10
- }
11
- return acc[key];
12
- }, payload);
13
- }
14
- const platformNormalizers = {
15
- stripe: {
16
- platform: 'stripe',
17
- category: 'payment',
18
- normalize: (payload) => ({
19
- category: 'payment',
20
- event: readPath(payload, 'type') === 'payment_intent.succeeded'
21
- ? 'payment.succeeded'
22
- : 'payment.unknown',
23
- amount: readPath(payload, 'data.object.amount_received')
24
- ?? readPath(payload, 'data.object.amount'),
25
- currency: String(readPath(payload, 'data.object.currency') ?? '').toUpperCase() || undefined,
26
- customer_id: readPath(payload, 'data.object.customer'),
27
- transaction_id: readPath(payload, 'data.object.id'),
28
- metadata: {},
29
- occurred_at: new Date().toISOString(),
30
- }),
31
- },
32
- polar: {
33
- platform: 'polar',
34
- category: 'payment',
35
- normalize: (payload) => ({
36
- category: 'payment',
37
- event: readPath(payload, 'event') === 'payment.completed'
38
- ? 'payment.succeeded'
39
- : 'payment.unknown',
40
- amount: readPath(payload, 'payload.amount_cents'),
41
- currency: String(readPath(payload, 'payload.currency_code') ?? '').toUpperCase() || undefined,
42
- customer_id: readPath(payload, 'payload.customer_id'),
43
- transaction_id: readPath(payload, 'payload.transaction_id'),
44
- metadata: {},
45
- occurred_at: new Date().toISOString(),
46
- }),
47
- },
48
- clerk: {
49
- platform: 'clerk',
50
- category: 'auth',
51
- normalize: (payload) => ({
52
- category: 'auth',
53
- event: readPath(payload, 'type') || 'auth.unknown',
54
- user_id: readPath(payload, 'data.id'),
55
- email: readPath(payload, 'data.email_addresses.0.email_address'),
56
- metadata: {},
57
- occurred_at: new Date().toISOString(),
58
- }),
59
- },
60
- vercel: {
61
- platform: 'vercel',
62
- category: 'infrastructure',
63
- normalize: (payload) => ({
64
- category: 'infrastructure',
65
- event: readPath(payload, 'type') || 'deployment.unknown',
66
- project_id: readPath(payload, 'payload.project.id'),
67
- deployment_id: readPath(payload, 'payload.deployment.id'),
68
- status: 'unknown',
69
- metadata: {},
70
- occurred_at: new Date().toISOString(),
71
- }),
72
- },
73
- };
74
- function getPlatformNormalizationCategory(platform) {
75
- return platformNormalizers[platform]?.category || null;
76
- }
77
- function getPlatformsByCategory(category) {
78
- return Object.values(platformNormalizers)
79
- .filter((spec) => !!spec)
80
- .filter((spec) => spec.category === category)
81
- .map((spec) => spec.platform);
82
- }
83
- function resolveNormalizeOptions(normalize) {
84
- if (typeof normalize === 'boolean') {
85
- return {
86
- enabled: normalize,
87
- category: undefined,
88
- includeRaw: true,
89
- };
90
- }
91
- return {
92
- enabled: normalize?.enabled ?? true,
93
- category: normalize?.category,
94
- includeRaw: normalize?.includeRaw ?? true,
95
- };
96
- }
97
- function buildUnknownNormalizedPayload(platform, payload, category, includeRaw, warning) {
98
- return {
99
- category: category || 'infrastructure',
100
- event: payload?.type ?? payload?.event ?? 'unknown',
101
- _platform: platform,
102
- _raw: includeRaw ? payload : undefined,
103
- warning,
104
- occurred_at: new Date().toISOString(),
105
- };
106
- }
107
- function normalizePayload(platform, payload, normalize) {
108
- const options = resolveNormalizeOptions(normalize);
109
- if (!options.enabled) {
110
- return payload;
111
- }
112
- const spec = platformNormalizers[platform];
113
- const inferredCategory = spec?.category;
114
- if (!spec) {
115
- return buildUnknownNormalizedPayload(platform, payload, options.category, options.includeRaw);
116
- }
117
- if (options.category && options.category !== inferredCategory) {
118
- return buildUnknownNormalizedPayload(platform, payload, inferredCategory, options.includeRaw, `Requested normalization category '${options.category}' does not match platform category '${inferredCategory}'`);
119
- }
120
- const normalized = spec.normalize(payload);
121
- return {
122
- ...normalized,
123
- _platform: platform,
124
- _raw: options.includeRaw ? payload : undefined,
125
- };
126
- }
@@ -1,13 +0,0 @@
1
- import { BaseTemplate, UpdateSchemaInput, UserSchema } from '../types';
2
- export interface StorageAdapter {
3
- saveSchema(schema: UserSchema): Promise<void>;
4
- getSchema(id: string): Promise<UserSchema | null>;
5
- updateSchema(id: string, updates: UpdateSchemaInput): Promise<void>;
6
- deleteSchema(id: string): Promise<void>;
7
- listSchemas(userId: string): Promise<UserSchema[]>;
8
- getBaseTemplate(id: string): Promise<BaseTemplate | null>;
9
- listBaseTemplates(): Promise<BaseTemplate[]>;
10
- }
11
- export interface NormalizationStorageOptions {
12
- adapter: StorageAdapter;
13
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,12 +0,0 @@
1
- import { BaseTemplate, UpdateSchemaInput, UserSchema } from '../types';
2
- import { StorageAdapter } from './interface';
3
- export declare class InMemoryStorageAdapter implements StorageAdapter {
4
- private schemas;
5
- saveSchema(schema: UserSchema): Promise<void>;
6
- getSchema(id: string): Promise<UserSchema | null>;
7
- updateSchema(id: string, updates: UpdateSchemaInput): Promise<void>;
8
- deleteSchema(id: string): Promise<void>;
9
- listSchemas(userId: string): Promise<UserSchema[]>;
10
- getBaseTemplate(id: string): Promise<BaseTemplate | null>;
11
- listBaseTemplates(): Promise<BaseTemplate[]>;
12
- }