@hookflo/tern 1.0.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +70 -10
  2. package/dist/normalization/index.d.ts +20 -0
  3. package/dist/normalization/index.js +78 -0
  4. package/dist/normalization/providers/payment/paypal.d.ts +2 -0
  5. package/dist/normalization/providers/payment/paypal.js +12 -0
  6. package/dist/normalization/providers/payment/razorpay.d.ts +2 -0
  7. package/dist/normalization/providers/payment/razorpay.js +13 -0
  8. package/dist/normalization/providers/payment/stripe.d.ts +2 -0
  9. package/dist/normalization/providers/payment/stripe.js +13 -0
  10. package/dist/normalization/providers/registry.d.ts +5 -0
  11. package/dist/normalization/providers/registry.js +23 -0
  12. package/dist/normalization/storage/interface.d.ts +13 -0
  13. package/dist/normalization/storage/interface.js +2 -0
  14. package/dist/normalization/storage/memory.d.ts +12 -0
  15. package/dist/normalization/storage/memory.js +39 -0
  16. package/dist/normalization/templates/base/auth.d.ts +2 -0
  17. package/dist/normalization/templates/base/auth.js +22 -0
  18. package/dist/normalization/templates/base/ecommerce.d.ts +2 -0
  19. package/dist/normalization/templates/base/ecommerce.js +25 -0
  20. package/dist/normalization/templates/base/payment.d.ts +2 -0
  21. package/dist/normalization/templates/base/payment.js +25 -0
  22. package/dist/normalization/templates/registry.d.ts +6 -0
  23. package/dist/normalization/templates/registry.js +22 -0
  24. package/dist/normalization/transformer/engine.d.ts +11 -0
  25. package/dist/normalization/transformer/engine.js +86 -0
  26. package/dist/normalization/transformer/validator.d.ts +12 -0
  27. package/dist/normalization/transformer/validator.js +56 -0
  28. package/dist/normalization/types.d.ts +79 -0
  29. package/dist/normalization/types.js +2 -0
  30. package/dist/platforms/algorithms.d.ts +1 -1
  31. package/dist/platforms/algorithms.js +89 -89
  32. package/dist/verifiers/algorithms.d.ts +2 -2
  33. package/dist/verifiers/algorithms.js +62 -62
  34. package/dist/verifiers/base.d.ts +1 -1
  35. package/dist/verifiers/custom-algorithms.d.ts +2 -2
  36. package/dist/verifiers/custom-algorithms.js +8 -8
  37. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Tern - Algorithm Agnostic Webhook Verification Framework
2
2
 
3
3
  A robust, algorithm-agnostic webhook verification framework that supports multiple platforms with accurate signature verification and payload retrieval.
4
+ The same framework that secures webhook verification at [Hookflo](https://hookflo.com).
5
+
6
+ ```bash
7
+ npm install @hookflo/tern
8
+ ```
4
9
 
5
10
  [![npm version](https://img.shields.io/npm/v/@hookflo/tern)](https://www.npmjs.com/package/@hookflo/tern)
6
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue)](https://www.typescriptlang.org/)
@@ -9,12 +14,13 @@ A robust, algorithm-agnostic webhook verification framework that supports multip
9
14
  Tern is a zero-dependency TypeScript framework for robust webhook verification across multiple platforms and algorithms.
10
15
 
11
16
  <img width="1396" height="470" style="border-radius: 10px" alt="tern bird nature" src="https://github.com/user-attachments/assets/5f0da3e6-1aba-4f88-a9d7-9d8698845c39" />
17
+
12
18
  ## Features
13
19
 
14
20
  - **Algorithm Agnostic**: Decouples platform logic from signature verification — verify based on cryptographic algorithm, not hardcoded platform rules.
15
21
  Supports HMAC-SHA256, HMAC-SHA1, HMAC-SHA512, and custom algorithms
16
22
 
17
- - **Platform Specific**: Accurate implementations for Stripe, GitHub, Clerk, and other platforms
23
+ - **Platform Specific**: Accurate implementations for **Stripe, GitHub, Supabase, Clerk**, and other platforms
18
24
  - **Flexible Configuration**: Custom signature configurations for any webhook format
19
25
  - **Type Safe**: Full TypeScript support with comprehensive type definitions
20
26
  - **Framework Agnostic**: Works with Express.js, Next.js, Cloudflare Workers, and more
@@ -40,15 +46,18 @@ npm install @hookflo/tern
40
46
  ### Basic Usage
41
47
 
42
48
  ```typescript
43
- import { WebhookVerificationService } from '@hookflo/tern';
49
+ import { WebhookVerificationService, platformManager } from '@hookflo/tern';
44
50
 
45
- // Verify a Stripe webhook
51
+ // Method 1: Using the service (recommended)
46
52
  const result = await WebhookVerificationService.verifyWithPlatformConfig(
47
53
  request,
48
54
  'stripe',
49
55
  'whsec_your_stripe_webhook_secret'
50
56
  );
51
57
 
58
+ // Method 2: Using platform manager (for platform-specific operations)
59
+ const stripeResult = await platformManager.verify(request, 'stripe', 'whsec_your_secret');
60
+
52
61
  if (result.isValid) {
53
62
  console.log('Webhook verified!', result.payload);
54
63
  } else {
@@ -56,6 +65,21 @@ if (result.isValid) {
56
65
  }
57
66
  ```
58
67
 
68
+ ### Platform-Specific Usage
69
+
70
+ ```typescript
71
+ import { platformManager } from '@hookflo/tern';
72
+
73
+ // Run tests for a specific platform
74
+ const testsPassed = await platformManager.runPlatformTests('stripe');
75
+
76
+ // Get platform configuration
77
+ const config = platformManager.getConfig('stripe');
78
+
79
+ // Get platform documentation
80
+ const docs = platformManager.getDocumentation('stripe');
81
+ ```
82
+
59
83
  ### Platform-Specific Configurations
60
84
 
61
85
  ```typescript
@@ -330,16 +354,33 @@ interface WebhookConfig {
330
354
 
331
355
  ## Testing
332
356
 
333
- Run the test suite:
357
+ ### Run All Tests
334
358
 
335
359
  ```bash
336
360
  npm test
337
361
  ```
338
362
 
339
- Run examples:
363
+ ### Platform-Specific Testing
340
364
 
341
365
  ```bash
342
- npm run examples
366
+ # Test a specific platform
367
+ npm run test:platform stripe
368
+
369
+ # Test all platforms
370
+ npm run test:all
371
+ ```
372
+
373
+ ### Documentation and Analysis
374
+
375
+ ```bash
376
+ # Fetch platform documentation
377
+ npm run docs:fetch
378
+
379
+ # Generate diffs between versions
380
+ npm run docs:diff
381
+
382
+ # Analyze changes and generate reports
383
+ npm run docs:analyze
343
384
  ```
344
385
 
345
386
  ## Examples
@@ -348,11 +389,30 @@ See the [examples.ts](./src/examples.ts) file for comprehensive usage examples.
348
389
 
349
390
  ## Contributing
350
391
 
392
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for detailed information on how to:
393
+
394
+ - Set up your development environment
395
+ - Add new platforms
396
+ - Write tests
397
+ - Submit pull requests
398
+ - Follow our code style guidelines
399
+
400
+ ### Quick Start for Contributors
401
+
351
402
  1. Fork the repository
352
- 2. Create a feature branch
353
- 3. Make your changes
354
- 4. Add tests for new functionality
355
- 5. Submit a pull request
403
+ 2. Clone your fork: `git clone https://github.com/your-username/tern.git`
404
+ 3. Create a feature branch: `git checkout -b feature/your-feature-name`
405
+ 4. Make your changes
406
+ 5. Run tests: `npm test`
407
+ 6. Submit a pull request
408
+
409
+ ### Adding a New Platform
410
+
411
+ See our [Platform Development Guide](CONTRIBUTING.md#adding-new-platforms) for step-by-step instructions on adding support for new webhook platforms.
412
+
413
+ ## Code of Conduct
414
+
415
+ This project adheres to our [Code of Conduct](CODE_OF_CONDUCT.md). Please read it before contributing.
356
416
 
357
417
  ## 📄 License
358
418
 
@@ -0,0 +1,20 @@
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';
@@ -0,0 +1,78 @@
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; } });
@@ -0,0 +1,2 @@
1
+ import { ProviderMapping } from '../../types';
2
+ export declare const paypalDefaultMapping: ProviderMapping;
@@ -0,0 +1,12 @@
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
+ };
@@ -0,0 +1,2 @@
1
+ import { ProviderMapping } from '../../types';
2
+ export declare const razorpayDefaultMapping: ProviderMapping;
@@ -0,0 +1,13 @@
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
+ };
@@ -0,0 +1,2 @@
1
+ import { ProviderMapping } from '../../types';
2
+ export declare const stripeDefaultMapping: ProviderMapping;
@@ -0,0 +1,13 @@
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
+ };
@@ -0,0 +1,5 @@
1
+ import { ProviderInfo } from '../types';
2
+ export declare const providerRegistry: {
3
+ list(category?: ProviderInfo["category"]): ProviderInfo[];
4
+ getById(id: string): ProviderInfo | undefined;
5
+ };
@@ -0,0 +1,23 @@
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: 'auth0', name: 'Auth0', category: 'auth' },
10
+ { id: 'supabase', name: 'Supabase', category: 'auth' },
11
+ { id: 'shopify', name: 'Shopify', category: 'ecommerce' },
12
+ { id: 'woocommerce', name: 'WooCommerce', category: 'ecommerce' },
13
+ ];
14
+ exports.providerRegistry = {
15
+ list(category) {
16
+ if (!category)
17
+ return providers;
18
+ return providers.filter((p) => p.category === category);
19
+ },
20
+ getById(id) {
21
+ return providers.find((p) => p.id === id);
22
+ },
23
+ };
@@ -0,0 +1,13 @@
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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,12 @@
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
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryStorageAdapter = void 0;
4
+ const registry_1 = require("../templates/registry");
5
+ class InMemoryStorageAdapter {
6
+ constructor() {
7
+ this.schemas = new Map();
8
+ }
9
+ async saveSchema(schema) {
10
+ this.schemas.set(schema.id, schema);
11
+ }
12
+ async getSchema(id) {
13
+ return this.schemas.get(id) ?? null;
14
+ }
15
+ async updateSchema(id, updates) {
16
+ const existing = this.schemas.get(id);
17
+ if (!existing)
18
+ return;
19
+ const updated = {
20
+ ...existing,
21
+ ...updates,
22
+ updatedAt: new Date(),
23
+ };
24
+ this.schemas.set(id, updated);
25
+ }
26
+ async deleteSchema(id) {
27
+ this.schemas.delete(id);
28
+ }
29
+ async listSchemas(userId) {
30
+ return Array.from(this.schemas.values()).filter((s) => s.userId === userId);
31
+ }
32
+ async getBaseTemplate(id) {
33
+ return registry_1.templateRegistry.getById(id) ?? null;
34
+ }
35
+ async listBaseTemplates() {
36
+ return registry_1.templateRegistry.listAll();
37
+ }
38
+ }
39
+ exports.InMemoryStorageAdapter = InMemoryStorageAdapter;
@@ -0,0 +1,2 @@
1
+ import { BaseTemplate } from '../../types';
2
+ export declare const authBaseTemplate: BaseTemplate;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authBaseTemplate = void 0;
4
+ exports.authBaseTemplate = {
5
+ id: 'auth_v1',
6
+ category: 'auth',
7
+ version: '1.0.0',
8
+ fields: [
9
+ {
10
+ id: 'event_type', name: 'event_type', type: 'string', required: true,
11
+ },
12
+ {
13
+ id: 'user_id', name: 'user_id', type: 'string', required: true,
14
+ },
15
+ {
16
+ id: 'email', name: 'email', type: 'string', required: false,
17
+ },
18
+ {
19
+ id: 'status', name: 'status', type: 'string', required: true,
20
+ },
21
+ ],
22
+ };
@@ -0,0 +1,2 @@
1
+ import { BaseTemplate } from '../../types';
2
+ export declare const ecommerceBaseTemplate: BaseTemplate;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ecommerceBaseTemplate = void 0;
4
+ exports.ecommerceBaseTemplate = {
5
+ id: 'ecommerce_v1',
6
+ category: 'ecommerce',
7
+ version: '1.0.0',
8
+ fields: [
9
+ {
10
+ id: 'event_type', name: 'event_type', type: 'string', required: true,
11
+ },
12
+ {
13
+ id: 'order_id', name: 'order_id', type: 'string', required: true,
14
+ },
15
+ {
16
+ id: 'total', name: 'total', type: 'number', required: true,
17
+ },
18
+ {
19
+ id: 'currency', name: 'currency', type: 'string', required: true,
20
+ },
21
+ {
22
+ id: 'customer_id', name: 'customer_id', type: 'string', required: false,
23
+ },
24
+ ],
25
+ };
@@ -0,0 +1,2 @@
1
+ import { BaseTemplate } from '../../types';
2
+ export declare const paymentBaseTemplate: BaseTemplate;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.paymentBaseTemplate = void 0;
4
+ exports.paymentBaseTemplate = {
5
+ id: 'payment_v1',
6
+ category: 'payment',
7
+ version: '1.0.0',
8
+ fields: [
9
+ {
10
+ id: 'event_type', name: 'event_type', type: 'string', required: true, description: 'Type of payment event',
11
+ },
12
+ {
13
+ id: 'amount', name: 'amount', type: 'number', required: true, description: 'Amount in the smallest currency unit',
14
+ },
15
+ {
16
+ id: 'currency', name: 'currency', type: 'string', required: true, description: 'Three-letter currency code',
17
+ },
18
+ {
19
+ id: 'transaction_id', name: 'transaction_id', type: 'string', required: true, description: 'Unique transaction identifier',
20
+ },
21
+ {
22
+ id: 'customer_id', name: 'customer_id', type: 'string', required: false, description: 'Customer identifier',
23
+ },
24
+ ],
25
+ };
@@ -0,0 +1,6 @@
1
+ import { BaseTemplate, TemplateCategory } from '../types';
2
+ export declare const templateRegistry: {
3
+ getById(id: string): BaseTemplate | undefined;
4
+ listByCategory(category: TemplateCategory): BaseTemplate[];
5
+ listAll(): BaseTemplate[];
6
+ };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.templateRegistry = void 0;
4
+ const payment_1 = require("./base/payment");
5
+ const auth_1 = require("./base/auth");
6
+ const ecommerce_1 = require("./base/ecommerce");
7
+ const templates = {
8
+ [payment_1.paymentBaseTemplate.id]: payment_1.paymentBaseTemplate,
9
+ [auth_1.authBaseTemplate.id]: auth_1.authBaseTemplate,
10
+ [ecommerce_1.ecommerceBaseTemplate.id]: ecommerce_1.ecommerceBaseTemplate,
11
+ };
12
+ exports.templateRegistry = {
13
+ getById(id) {
14
+ return templates[id];
15
+ },
16
+ listByCategory(category) {
17
+ return Object.values(templates).filter((t) => t.category === category);
18
+ },
19
+ listAll() {
20
+ return Object.values(templates);
21
+ },
22
+ };
@@ -0,0 +1,11 @@
1
+ import { NormalizedResult, TransformParams } from '../types';
2
+ import { StorageAdapter } from '../storage/interface';
3
+ import { SchemaValidator } from './validator';
4
+ export declare class NormalizationEngine {
5
+ private readonly storage;
6
+ private readonly validator;
7
+ constructor(storage: StorageAdapter, validator?: SchemaValidator);
8
+ transform(params: TransformParams): Promise<NormalizedResult>;
9
+ private extractValue;
10
+ private applyTransform;
11
+ }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NormalizationEngine = void 0;
4
+ const registry_1 = require("../templates/registry");
5
+ const validator_1 = require("./validator");
6
+ class NormalizationEngine {
7
+ constructor(storage, validator = new validator_1.SchemaValidator()) {
8
+ this.storage = storage;
9
+ this.validator = validator;
10
+ }
11
+ async transform(params) {
12
+ const { rawPayload, provider, schemaId } = params;
13
+ const schema = await this.storage.getSchema(schemaId);
14
+ if (!schema)
15
+ throw new Error(`Schema not found: ${schemaId}`);
16
+ const baseTemplate = await this.storage.getBaseTemplate(schema.baseTemplateId) || registry_1.templateRegistry.getById(schema.baseTemplateId);
17
+ if (!baseTemplate)
18
+ throw new Error(`Base template not found: ${schema.baseTemplateId}`);
19
+ const validation = this.validator.validateSchema(schema, baseTemplate);
20
+ if (!validation.valid) {
21
+ throw new Error(`Invalid schema: ${validation.errors.join('; ')}`);
22
+ }
23
+ const providerMapping = schema.providerMappings.find((m) => m.provider === provider);
24
+ if (!providerMapping)
25
+ throw new Error(`No mapping found for provider: ${provider}`);
26
+ const normalized = {};
27
+ for (const field of schema.fields) {
28
+ if (!field.enabled)
29
+ continue;
30
+ const mapping = providerMapping.fieldMappings.find((m) => m.schemaFieldId === field.id);
31
+ if (mapping) {
32
+ const value = this.extractValue(rawPayload, mapping.providerPath);
33
+ const finalValue = this.applyTransform(value, mapping.transform);
34
+ normalized[field.name] = finalValue ?? field.defaultValue;
35
+ }
36
+ else if (field.required) {
37
+ if (field.defaultValue !== undefined) {
38
+ normalized[field.name] = field.defaultValue;
39
+ }
40
+ else {
41
+ throw new Error(`Required field ${field.name} has no mapping`);
42
+ }
43
+ }
44
+ }
45
+ const outValidation = this.validator.validateOutput(normalized, schema, baseTemplate);
46
+ if (!outValidation.valid) {
47
+ throw new Error(`Normalized output invalid: ${outValidation.errors.join('; ')}`);
48
+ }
49
+ return {
50
+ normalized,
51
+ meta: {
52
+ provider,
53
+ schemaId,
54
+ schemaVersion: schema.baseTemplateId,
55
+ transformedAt: new Date(),
56
+ },
57
+ };
58
+ }
59
+ extractValue(obj, path) {
60
+ if (!path)
61
+ return undefined;
62
+ return path.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), obj);
63
+ }
64
+ applyTransform(value, transform) {
65
+ if (transform == null)
66
+ return value;
67
+ if (value == null)
68
+ return value;
69
+ if (transform === 'toUpperCase')
70
+ return String(value).toUpperCase();
71
+ if (transform === 'toLowerCase')
72
+ return String(value).toLowerCase();
73
+ if (transform === 'toNumber')
74
+ return typeof value === 'number' ? value : Number(value);
75
+ if (transform.startsWith('divide:')) {
76
+ const denominator = Number(transform.split(':')[1]);
77
+ return Number(value) / denominator;
78
+ }
79
+ if (transform.startsWith('multiply:')) {
80
+ const factor = Number(transform.split(':')[1]);
81
+ return Number(value) * factor;
82
+ }
83
+ return value;
84
+ }
85
+ }
86
+ exports.NormalizationEngine = NormalizationEngine;
@@ -0,0 +1,12 @@
1
+ import { BaseTemplate, UserSchema } from '../types';
2
+ export declare class SchemaValidator {
3
+ validateSchema(userSchema: UserSchema, baseTemplate: BaseTemplate): {
4
+ valid: boolean;
5
+ errors: string[];
6
+ };
7
+ validateOutput(output: Record<string, unknown>, userSchema: UserSchema, baseTemplate: BaseTemplate): {
8
+ valid: boolean;
9
+ errors: string[];
10
+ };
11
+ private matchesType;
12
+ }