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
JavaScript
;
/**
* 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