UNPKG

@forzalabs/remora

Version:

A powerful CLI tool for seamless data translation.

226 lines (225 loc) 11.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Affirm_1 = __importDefault(require("../../core/Affirm")); const Algo_1 = __importDefault(require("../../core/Algo")); const TypeCaster_1 = __importDefault(require("./TypeCaster")); class TransformationEngineClass { constructor() { this.apply = (consumer, data) => { var _a; (0, Affirm_1.default)(consumer, 'Invalid consumer'); Affirm_1.default.hasValue(data, 'Invalid data'); const fieldsToTransform = consumer.fields.filter(field => Algo_1.default.hasVal(field.transform)); Affirm_1.default.hasItems(fieldsToTransform, 'No fields with transformations'); // Process the data records in place to improve performance instead of copying to a new array for (const record of data) { for (const field of fieldsToTransform) { if (!field.transform) continue; const fieldKey = (_a = field.alias) !== null && _a !== void 0 ? _a : field.key; const value = record[fieldKey]; if (!Algo_1.default.hasVal(value) && Algo_1.default.hasVal(field.default)) record[fieldKey] = field.default; else if (!Algo_1.default.hasVal(value)) continue; try { record[fieldKey] = this.applyTransformations(value, field.transform, field); } catch (error) { switch (field.onError) { case 'set_default': record[fieldKey] = field.default; break; case 'skip': break; case 'fail': default: throw error; } } } } return data; }; this.applyTransformations = (value, transformations, field) => { var _a; if (Array.isArray(transformations)) { // Process array transformations without creating intermediate arrays let result = value; for (const transform of transformations) { result = this.applyTransformations(result, transform, field); } return result; } // Single transformation if ('cast' in transformations) { const casted = TypeCaster_1.default.cast(value, transformations.cast); if (isNaN(casted) && transformations.cast === 'number') throw new Error(`Cannot cast non-numeric value in field '${field.key}'`); return casted; } if ('multiply' in transformations) { const num = TypeCaster_1.default.cast(value, 'number'); if (isNaN(num)) throw new Error(`Cannot multiply non-numeric value in field '${field.key}'`); return num * transformations.multiply; } if ('add' in transformations) { const num = TypeCaster_1.default.cast(value, 'number'); if (isNaN(num)) throw new Error(`Cannot add to non-numeric value in field '${field.key}'`); return num + transformations.add; } if ('extract' in transformations) { const date = TypeCaster_1.default.cast(value, 'date'); if (isNaN(date.getTime())) throw new Error(`Invalid date for extraction in field '${field.key}'`); switch (transformations.extract) { case 'year': return date.getFullYear(); case 'month': return date.getMonth() + 1; // 1-based month case 'day': return date.getDate(); case 'hour': return date.getHours(); case 'minute': return date.getMinutes(); } } if ('concat' in transformations) { if (!Array.isArray(value)) throw new Error(`Cannot concat non-array value in field '${field.key}'`); return value.join(transformations.concat.separator); } if ('split' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot split non-string value in field '${field.key}'`); const parts = value.split(transformations.split.separator); if (transformations.split.index >= parts.length) { throw new Error(`Split index ${transformations.split.index} out of bounds in field '${field.key}'`); } return parts[transformations.split.index]; } if ('regex_match' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot apply regex_match to non-string value in field '${field.key}'`); try { const regex = new RegExp(transformations.regex_match.pattern, transformations.regex_match.flags); return regex.test(value); } catch (error) { throw new Error(`Invalid regex pattern in field '${field.key}': ${error.message}`); } } if ('regex_replace' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot apply regex_replace to non-string value in field '${field.key}'`); try { const regex = new RegExp(transformations.regex_replace.pattern, transformations.regex_replace.flags); return value.replace(regex, transformations.regex_replace.replacement); } catch (error) { throw new Error(`Invalid regex pattern in field '${field.key}': ${error.message}`); } } if ('regex_extract' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot apply regex_extract to non-string value in field '${field.key}'`); try { const regex = new RegExp(transformations.regex_extract.pattern, transformations.regex_extract.flags); const matches = value.match(regex); if (!matches) return null; const groupIndex = transformations.regex_extract.group; return (_a = matches[groupIndex]) !== null && _a !== void 0 ? _a : null; } catch (error) { throw new Error(`Invalid regex pattern in field '${field.key}': ${error.message}`); } } if ('trim' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot trim non-string value in field '${field.key}'`); return value.trim(); } if ('to_lowercase' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot convert non-string value to lowercase in field '${field.key}'`); return value.toLowerCase(); } if ('to_uppercase' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot convert non-string value to uppercase in field '${field.key}'`); return value.toUpperCase(); } if ('capitalize' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot capitalize non-string value in field '${field.key}'`); return value.charAt(0).toUpperCase() + value.slice(1); } if ('substring' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot take substring of non-string value in field '${field.key}'`); const { start, end } = transformations.substring; return end !== undefined ? value.substring(start, end) : value.substring(start); } if ('pad_start' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot pad non-string value in field '${field.key}'`); const { length, char } = transformations.pad_start; if (char.length !== 1) throw new Error(`Pad character must be exactly one character in field '${field.key}'`); return value.padStart(length, char); } if ('pad_end' in transformations) { if (typeof value !== 'string') throw new Error(`Cannot pad non-string value in field '${field.key}'`); const { length, char } = transformations.pad_end; if (char.length !== 1) throw new Error(`Pad character must be exactly one character in field '${field.key}'`); return value.padEnd(length, char); } if ('prepend' in transformations) return transformations.prepend + TypeCaster_1.default.cast(value, 'string'); if ('append' in transformations) return TypeCaster_1.default.cast(value, 'string') + transformations.append; if ('conditional' in transformations) { for (const clause of transformations.conditional.clauses) { if (this.evaluateCondition(value, clause.if)) { return clause.then; } } return transformations.conditional.else !== undefined ? transformations.conditional.else : value; } return value; }; this.evaluateCondition = (value, condition) => { if ('greater_than' in condition) { return TypeCaster_1.default.cast(value, 'number') > condition.greater_than; } if ('greater_than_or_equal' in condition) { return TypeCaster_1.default.cast(value, 'number') >= condition.greater_than_or_equal; } if ('less_than' in condition) { return TypeCaster_1.default.cast(value, 'number') < condition.less_than; } if ('less_than_or_equal' in condition) { return TypeCaster_1.default.cast(value, 'number') <= condition.less_than_or_equal; } if ('equals' in condition) { return value === condition.equals; } if ('not_equals' in condition) { return value !== condition.not_equals; } if ('in' in condition) { return condition.in.includes(value); } if ('not_in' in condition) { return !condition.not_in.includes(value); } return false; }; } } const TransformationEngine = new TransformationEngineClass(); exports.default = TransformationEngine;