@hookflo/tern
Version:
A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms
87 lines (86 loc) • 3.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NormalizationEngine = void 0;
const registry_1 = require("../templates/registry");
const validator_1 = require("./validator");
class NormalizationEngine {
constructor(storage, validator = new validator_1.SchemaValidator()) {
this.storage = storage;
this.validator = validator;
}
async transform(params) {
const { rawPayload, provider, schemaId } = params;
const schema = await this.storage.getSchema(schemaId);
if (!schema)
throw new Error(`Schema not found: ${schemaId}`);
const baseTemplate = await this.storage.getBaseTemplate(schema.baseTemplateId) || registry_1.templateRegistry.getById(schema.baseTemplateId);
if (!baseTemplate)
throw new Error(`Base template not found: ${schema.baseTemplateId}`);
const validation = this.validator.validateSchema(schema, baseTemplate);
if (!validation.valid) {
throw new Error(`Invalid schema: ${validation.errors.join('; ')}`);
}
const providerMapping = schema.providerMappings.find((m) => m.provider === provider);
if (!providerMapping)
throw new Error(`No mapping found for provider: ${provider}`);
const normalized = {};
for (const field of schema.fields) {
if (!field.enabled)
continue;
const mapping = providerMapping.fieldMappings.find((m) => m.schemaFieldId === field.id);
if (mapping) {
const value = this.extractValue(rawPayload, mapping.providerPath);
const finalValue = this.applyTransform(value, mapping.transform);
normalized[field.name] = finalValue ?? field.defaultValue;
}
else if (field.required) {
if (field.defaultValue !== undefined) {
normalized[field.name] = field.defaultValue;
}
else {
throw new Error(`Required field ${field.name} has no mapping`);
}
}
}
const outValidation = this.validator.validateOutput(normalized, schema, baseTemplate);
if (!outValidation.valid) {
throw new Error(`Normalized output invalid: ${outValidation.errors.join('; ')}`);
}
return {
normalized,
meta: {
provider,
schemaId,
schemaVersion: schema.baseTemplateId,
transformedAt: new Date(),
},
};
}
extractValue(obj, path) {
if (!path)
return undefined;
return path.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), obj);
}
applyTransform(value, transform) {
if (transform == null)
return value;
if (value == null)
return value;
if (transform === 'toUpperCase')
return String(value).toUpperCase();
if (transform === 'toLowerCase')
return String(value).toLowerCase();
if (transform === 'toNumber')
return typeof value === 'number' ? value : Number(value);
if (transform.startsWith('divide:')) {
const denominator = Number(transform.split(':')[1]);
return Number(value) / denominator;
}
if (transform.startsWith('multiply:')) {
const factor = Number(transform.split(':')[1]);
return Number(value) * factor;
}
return value;
}
}
exports.NormalizationEngine = NormalizationEngine;