@forzalabs/remora
Version:
A powerful CLI tool for seamless data translation.
226 lines (225 loc) • 11.6 kB
JavaScript
;
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;