UNPKG

rawsql-ts

Version:

High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

363 lines 13.8 kB
"use strict"; /** * Post-processor for transforming database values to appropriate TypeScript types * after JSON serialization from PostgreSQL */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TypeTransformers = exports.TypeTransformationPostProcessor = void 0; exports.transformDatabaseResult = transformDatabaseResult; /** * Applies type transformations to JSON results from PostgreSQL */ class TypeTransformationPostProcessor { constructor(config = {}) { this.config = { enableValueBasedDetection: true, strictDateDetection: false, ...config }; } /** * Transform a single result object * @param result The result object from PostgreSQL JSON query * @returns Transformed result with proper TypeScript types */ transformResult(result) { if (result === null || result === undefined) { return result; } if (Array.isArray(result)) { return result.map(item => this.transformSingleObject(item)); } return this.transformSingleObject(result); } /** * Transform a single object recursively */ transformSingleObject(obj) { var _a; if (obj === null || obj === undefined || typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return obj.map(item => this.transformSingleObject(item)); } const transformed = {}; for (const [key, value] of Object.entries(obj)) { if (value === null || value === undefined) { transformed[key] = value; continue; } // Check for column-specific transformation first (takes precedence) const columnTransform = (_a = this.config.columnTransformations) === null || _a === void 0 ? void 0 : _a[key]; if (columnTransform) { transformed[key] = this.applyTransformation(value, columnTransform); continue; } // Only apply value-based detection if enabled and no column mapping exists if (this.config.enableValueBasedDetection) { const detectedTransform = this.detectValueBasedTransformation(value); if (detectedTransform) { transformed[key] = this.applyTransformation(value, detectedTransform); continue; } } // Apply global transformations based on SQL type (if available) const globalTransform = this.config.globalTransformations && this.getGlobalTransformationForValue(value); if (globalTransform) { transformed[key] = this.applyTransformation(value, globalTransform); continue; } // Recursively transform nested objects if (typeof value === 'object' && !Array.isArray(value)) { transformed[key] = this.transformSingleObject(value); continue; } if (Array.isArray(value)) { transformed[key] = value.map(item => typeof item === 'object' ? this.transformSingleObject(item) : item); continue; } // No transformation needed transformed[key] = value; } return transformed; } /** * Detect value type and create appropriate transformation based on value characteristics * This is the core value-based detection logic */ detectValueBasedTransformation(value) { // Date string detection if (typeof value === 'string' && this.isDateString(value)) { return { sourceType: 'TIMESTAMP', targetType: 'Date', handleNull: true, validator: (v) => typeof v === 'string' && !isNaN(Date.parse(v)) }; } // BigInt detection (number > MAX_SAFE_INTEGER) if (typeof value === 'number' && !Number.isSafeInteger(value)) { return { sourceType: 'BIGINT', targetType: 'bigint', handleNull: true, validator: (v) => { try { if (typeof v === 'string' || typeof v === 'number' || typeof v === 'bigint' || typeof v === 'boolean') { BigInt(v); return true; } return false; } catch { return false; } } }; } // Large string number detection (potential BIGINT) if (typeof value === 'string' && /^\d{16,}$/.test(value)) { return { sourceType: 'BIGINT', targetType: 'bigint', handleNull: true, validator: (v) => { try { if (typeof v === 'string' || typeof v === 'number' || typeof v === 'bigint' || typeof v === 'boolean') { BigInt(v); return true; } return false; } catch { return false; } } }; } return null; } /** * Get global transformation for a specific value (if any match) * This is separate from value-based detection and relies on configured global rules */ getGlobalTransformationForValue(value) { if (!this.config.globalTransformations) { return null; } // This could be extended to match values against configured global rules // For now, it's a placeholder for future SQL-type-based global transformations return null; } /** * @deprecated Use detectValueBasedTransformation instead * Detect value type and get appropriate global transformation */ detectAndGetGlobalTransformation(value) { return this.detectValueBasedTransformation(value); } /** * Check if string is a valid date string * Supports both strict (ISO 8601 with T separator) and loose detection */ isDateString(value) { if (this.config.strictDateDetection) { // Strict: Only ISO 8601 with T separator (safer for user input) const strictIsoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})?$/; if (!strictIsoPattern.test(value)) { return false; } } else { // Loose: ISO 8601 date pattern (includes date-only strings) const isoDatePattern = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})?)?$/; if (!isoDatePattern.test(value)) { return false; } } const date = new Date(value); return !isNaN(date.getTime()); } /** * Apply a specific transformation to a value */ applyTransformation(value, transformation) { var _a; // Handle null values if (value === null || value === undefined) { return transformation.handleNull !== false ? value : null; } // Validate value if validator is provided if (transformation.validator && !transformation.validator(value)) { console.warn(`TypeTransformationPostProcessor: Value validation failed for ${value}`); return value; } try { switch (transformation.targetType) { case 'Date': return new Date(value); case 'bigint': // Handle both string and number inputs for BIGINT // For scientific notation numbers, convert to integer first if (typeof value === 'number') { // Convert scientific notation to integer string const integerValue = Math.trunc(value); return BigInt(integerValue.toString()); } return BigInt(value); case 'string': return value.toString(); case 'number': return typeof value === 'string' ? parseFloat(value) : Number(value); case 'object': return typeof value === 'string' ? JSON.parse(value) : value; case 'custom': if (transformation.customTransformer && ((_a = this.config.customTransformers) === null || _a === void 0 ? void 0 : _a[transformation.customTransformer])) { return this.config.customTransformers[transformation.customTransformer](value); } break; default: return value; } } catch (error) { console.warn(`TypeTransformationPostProcessor: Transformation failed for ${value}:`, error); return value; } return value; } /** * Create a default configuration for common PostgreSQL types * Enables value-based detection with loose date detection by default */ static createDefaultConfig() { return { enableValueBasedDetection: true, strictDateDetection: false, globalTransformations: { 'DATE': { sourceType: 'DATE', targetType: 'Date', handleNull: true, validator: (value) => typeof value === 'string' && !isNaN(Date.parse(value)) }, 'TIMESTAMP': { sourceType: 'TIMESTAMP', targetType: 'Date', handleNull: true, validator: (value) => typeof value === 'string' && !isNaN(Date.parse(value)) }, 'BIGINT': { sourceType: 'BIGINT', targetType: 'bigint', handleNull: true, validator: (value) => { try { if (typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') { BigInt(value); return true; } return false; } catch { return false; } } } } }; } /** * Create a safe configuration for handling user input * Disables value-based detection and uses strict date detection */ static createSafeConfig(columnMappings) { return { enableValueBasedDetection: false, strictDateDetection: true, columnTransformations: columnMappings || {}, globalTransformations: { 'DATE': { sourceType: 'DATE', targetType: 'Date', handleNull: true, validator: (value) => typeof value === 'string' && !isNaN(Date.parse(value)) }, 'TIMESTAMP': { sourceType: 'TIMESTAMP', targetType: 'Date', handleNull: true, validator: (value) => typeof value === 'string' && !isNaN(Date.parse(value)) }, 'BIGINT': { sourceType: 'BIGINT', targetType: 'bigint', handleNull: true, validator: (value) => { try { if (typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') { BigInt(value); return true; } return false; } catch { return false; } } } } }; } } exports.TypeTransformationPostProcessor = TypeTransformationPostProcessor; /** * Convenience function to create and apply transformations */ function transformDatabaseResult(result, config) { const processor = new TypeTransformationPostProcessor(config || TypeTransformationPostProcessor.createDefaultConfig()); return processor.transformResult(result); } /** * Type-safe transformation helpers */ exports.TypeTransformers = { /** * Transform date string to Date object */ toDate: (value) => { if (value === null || value === undefined) return null; const date = new Date(value); return isNaN(date.getTime()) ? null : date; }, /** * Transform numeric string to BigInt */ toBigInt: (value) => { if (value === null || value === undefined) return null; try { return BigInt(value); } catch { return null; } }, /** * Transform JSON string to object */ toObject: (value) => { if (value === null || value === undefined) return null; try { return JSON.parse(value); } catch { return null; } } }; //# sourceMappingURL=TypeTransformationPostProcessor.js.map