rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
392 lines • 14.8 kB
JavaScript
;
/**
* Unified JSON mapping converter that handles all supported formats
* and provides a single interface for mapping transformations.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonMappingConverter = void 0;
const ModelDrivenJsonMapping_1 = require("./ModelDrivenJsonMapping");
const EnhancedJsonMapping_1 = require("./EnhancedJsonMapping");
/**
* Type guard to check if input is valid JSON mapping input
*/
function isValidMappingInput(input) {
return input !== null &&
input !== undefined &&
typeof input === 'object';
}
/**
* Enhanced format conversion strategy.
*/
class EnhancedFormatStrategy {
detect(input) {
if (!isValidMappingInput(input)) {
return false;
}
const candidate = input;
if (!candidate ||
typeof candidate.rootName !== 'string' ||
!candidate.rootEntity ||
!Array.isArray(candidate.nestedEntities)) {
return false;
}
// Check if it has enhanced features
if (candidate.typeInfo || candidate.typeProtection || candidate.metadata) {
return true;
}
// Check if any column uses enhanced format (object with 'column' property)
const hasEnhancedColumns = (columns) => {
if (!columns || typeof columns !== 'object')
return false;
return Object.values(columns).some(col => typeof col === 'object' && col !== null && 'column' in col);
};
if (hasEnhancedColumns(candidate.rootEntity.columns)) {
return true;
}
return candidate.nestedEntities.some((entity) => entity && typeof entity === 'object' &&
hasEnhancedColumns(entity.columns));
}
convert(input) {
var _a, _b;
return {
format: 'enhanced',
mapping: (0, EnhancedJsonMapping_1.toLegacyMapping)(input),
typeProtection: (0, EnhancedJsonMapping_1.extractTypeProtection)(input),
originalInput: input,
metadata: {
typeInfo: input.typeInfo,
version: (_a = input.metadata) === null || _a === void 0 ? void 0 : _a.version,
description: (_b = input.metadata) === null || _b === void 0 ? void 0 : _b.description
}
};
}
}
/**
* Model-driven format conversion strategy.
*/
class ModelDrivenFormatStrategy {
detect(input) {
if (!isValidMappingInput(input)) {
return false;
}
const candidate = input;
return candidate &&
candidate.typeInfo &&
candidate.structure &&
typeof candidate.typeInfo.interface === 'string';
}
convert(input) {
// Use the existing convertModelDrivenMapping function to avoid code duplication
const converted = (0, ModelDrivenJsonMapping_1.convertModelDrivenMapping)(input);
return {
format: 'model-driven',
mapping: converted.jsonMapping,
typeProtection: converted.typeProtection,
originalInput: input,
metadata: {
typeInfo: input.typeInfo
}
};
}
}
/**
* Legacy format conversion strategy.
*/
class LegacyFormatStrategy {
detect(input) {
if (!isValidMappingInput(input)) {
return false;
}
const candidate = input;
if (!candidate ||
typeof candidate.rootName !== 'string' ||
!candidate.rootEntity ||
typeof candidate.rootEntity.columns !== 'object' ||
candidate.typeInfo || candidate.typeProtection || candidate.metadata) {
return false;
}
// Check if any column uses enhanced format (object with 'column' property)
const hasEnhancedColumns = (columns) => {
if (!columns || typeof columns !== 'object')
return false;
return Object.values(columns).some(col => typeof col === 'object' && col !== null && 'column' in col);
};
// If it has enhanced columns, it's not legacy format
if (hasEnhancedColumns(candidate.rootEntity.columns)) {
return false;
}
if (candidate.nestedEntities && Array.isArray(candidate.nestedEntities)) {
const hasEnhancedNested = candidate.nestedEntities.some((entity) => entity && typeof entity === 'object' && hasEnhancedColumns(entity.columns));
if (hasEnhancedNested) {
return false;
}
}
return true;
}
convert(input) {
return {
format: 'legacy',
mapping: input,
typeProtection: { protectedStringFields: [] },
originalInput: input
};
}
}
/**
* Unified JSON mapping converter that handles all supported formats using the Strategy pattern.
*
* This converter automatically detects the input format and applies the appropriate conversion
* strategy to transform any supported JSON mapping format into a standardized result.
*
* **Supported Formats:**
* - **Enhanced**: Rich format with metadata, type protection, and advanced column configurations
* - **Model-Driven**: TypeScript interface-based mapping with structured field definitions
* - **Legacy**: Simple format compatible with PostgresJsonQueryBuilder
*
* **Usage:**
* ```typescript
* const converter = new JsonMappingConverter();
* const result = converter.convert(someMapping);
* const legacyMapping = converter.toLegacyMapping(someMapping);
* ```
*
* @public
*/
class JsonMappingConverter {
/**
* Creates a new JsonMappingConverter with all supported strategies.
*
* Strategies are checked in order of specificity:
* 1. Enhanced format (most feature-rich)
* 2. Model-driven format (TypeScript-based)
* 3. Legacy format (fallback)
*/
constructor() {
this.strategies = [
new EnhancedFormatStrategy(),
new ModelDrivenFormatStrategy(),
new LegacyFormatStrategy()
];
}
/**
* Detects the format of the input mapping without performing conversion.
*
* This method uses the same strategy pattern as conversion but only returns
* the detected format type for inspection purposes.
*
* @param input - The JSON mapping to analyze
* @returns The detected mapping format type
*
* @throws {Error} When input format is not supported by any strategy
*
* @example
* ```typescript
* const format = converter.detectFormat(myMapping);
* console.log(`Detected format: ${format}`); // "enhanced", "model-driven", or "legacy"
* ```
*/
detectFormat(input) {
for (const strategy of this.strategies) {
if (strategy.detect(input)) {
const result = strategy.convert(input);
return result.format;
}
}
throw new Error('Unsupported JSON mapping format');
}
/**
* Converts any supported JSON mapping format to a comprehensive result with metadata.
*
* This is the primary conversion method that performs format detection and transformation
* in a single operation. The result includes the legacy mapping, type protection configuration,
* and metadata about the conversion process.
*
* @param input - The JSON mapping in any supported format (Enhanced, Model-Driven, or Legacy)
* @returns Complete conversion result with mapping, metadata, and type protection
*
* @throws {Error} When the input format is not recognized by any strategy
*
* @example
* ```typescript
* const result = converter.convert(enhancedMapping);
* console.log(`Format: ${result.format}`);
* console.log(`Type protection: ${result.typeProtection.protectedStringFields.length} fields`);
*
* // Use the converted mapping
* const queryBuilder = new PostgresJsonQueryBuilder(result.mapping);
* ```
*
* @see {@link toLegacyMapping} For simple mapping extraction
* @see {@link getTypeProtection} For type protection only
*/
convert(input) {
for (const strategy of this.strategies) {
if (strategy.detect(input)) {
return strategy.convert(input);
}
}
throw new Error('Unsupported JSON mapping format: Unable to detect a compatible strategy for the provided input');
}
/**
* Extracts only the legacy JsonMapping for direct use with PostgresJsonQueryBuilder.
*
* This convenience method performs the full conversion but returns only the mapping portion,
* discarding metadata and type protection information. Use this when you only need
* the mapping for query building and don't require additional metadata.
*
* @param input - The JSON mapping in any supported format
* @returns Legacy-format JsonMapping ready for PostgresJsonQueryBuilder
*
* @throws {Error} When the input format is not supported
*
* @example
* ```typescript
* const legacyMapping = converter.toLegacyMapping(modelDrivenMapping);
* const queryBuilder = new PostgresJsonQueryBuilder(legacyMapping);
* const query = queryBuilder.build(selectQuery);
* ```
*
* @see {@link convert} For full conversion with metadata
*/
toLegacyMapping(input) {
return this.convert(input).mapping;
}
/**
* Extracts type protection configuration for runtime type checking.
*
* Type protection helps identify fields that should be treated as strings
* to prevent injection attacks or type coercion issues. This is particularly
* useful when working with user input or external data sources.
*
* @param input - The JSON mapping in any supported format
* @returns Type protection configuration with protected field definitions
*
* @throws {Error} When the input format is not supported
*
* @example
* ```typescript
* const typeProtection = converter.getTypeProtection(enhancedMapping);
*
* // Apply type protection during data processing
* for (const field of typeProtection.protectedStringFields) {
* if (typeof data[field] !== 'string') {
* data[field] = String(data[field]);
* }
* }
* ```
*/
getTypeProtection(input) {
return this.convert(input).typeProtection;
}
/**
* Validates that the input mapping is well-formed and can be successfully converted.
*
* This method performs comprehensive validation without attempting conversion,
* returning an array of error messages for any issues found. An empty array
* indicates the mapping is valid and ready for conversion.
*
* **Validation Checks:**
* - Basic structure validation (object type, required fields)
* - Format-specific validation (Enhanced, Model-Driven, Legacy)
* - Column configuration validation
* - Type protection configuration validation
*
* @param input - The JSON mapping to validate
* @returns Array of validation error messages (empty if valid)
*
* @example
* ```typescript
* const errors = converter.validate(suspiciousMapping);
* if (errors.length > 0) {
* console.error('Validation failed:', errors);
* throw new Error(`Invalid mapping: ${errors.join(', ')}`);
* }
*
* // Safe to convert
* const result = converter.convert(suspiciousMapping);
* ```
*
* @see {@link convert} Performs conversion after implicit validation
*/
validate(input) {
const errors = [];
// Pre-validation checks
if (!input || typeof input !== 'object') {
errors.push('Input must be an object');
return errors;
}
// Check for rootName before attempting conversion
if (!('rootName' in input) || !input.rootName) {
errors.push('rootName is required');
}
try {
const result = this.convert(input);
// Basic validation
if (!result.mapping.rootName) {
errors.push('rootName is required');
}
if (!result.mapping.rootEntity) {
errors.push('rootEntity is required');
}
else {
if (!result.mapping.rootEntity.id) {
errors.push('rootEntity.id is required');
}
if (!result.mapping.rootEntity.columns) {
errors.push('rootEntity.columns is required');
}
}
// Validate nested entities
if (result.mapping.nestedEntities) {
for (const entity of result.mapping.nestedEntities) {
if (!entity.id) {
errors.push(`Nested entity missing id: ${entity.propertyName}`);
}
if (!entity.parentId) {
errors.push(`Nested entity missing parentId: ${entity.id}`);
}
if (!entity.propertyName) {
errors.push(`Nested entity missing propertyName: ${entity.id}`);
}
}
}
}
catch (error) {
// Only add conversion error if we haven't already found specific errors
if (errors.length === 0) {
errors.push(`Conversion failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
return errors;
}
/**
* Creates a new enhanced mapping from legacy mapping.
*/
upgradeToEnhanced(legacy, typeInfo) {
return {
rootName: legacy.rootName,
rootEntity: {
id: legacy.rootEntity.id,
name: legacy.rootEntity.name,
columns: legacy.rootEntity.columns
},
nestedEntities: legacy.nestedEntities.map(entity => ({
id: entity.id,
name: entity.name,
parentId: entity.parentId,
propertyName: entity.propertyName,
relationshipType: entity.relationshipType || 'object',
columns: entity.columns
})),
resultFormat: legacy.resultFormat,
emptyResult: legacy.emptyResult,
typeInfo,
metadata: {
version: '1.0',
description: 'Upgraded from legacy format'
}
};
}
}
exports.JsonMappingConverter = JsonMappingConverter;
//# sourceMappingURL=JsonMappingConverter.js.map