UNPKG

@nexica/nestjs-trpc

Version:
600 lines 28.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaGenerator = void 0; const error_handler_1 = require("../utils/error-handler"); class SchemaGenerator { constructor() { Object.defineProperty(this, "schemaRegistry", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "processedSchemas", { enumerable: true, configurable: true, writable: true, value: new Set() }); Object.defineProperty(this, "schemaNameCache", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "processingSchemas", { enumerable: true, configurable: true, writable: true, value: new Set() }); Object.defineProperty(this, "schemaHashCache", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "schemaProcessingQueue", { enumerable: true, configurable: true, writable: true, value: [] }); } clear() { this.schemaRegistry.clear(); this.processedSchemas.clear(); this.schemaNameCache.clear(); this.processingSchemas.clear(); this.schemaHashCache.clear(); this.schemaProcessingQueue = []; } getSchemaTypeDefinition(schemaName) { var _a; return ((_a = this.schemaRegistry.get(schemaName)) === null || _a === void 0 ? void 0 : _a.typeDefinition) || ''; } collectSchemas(routerName, procedures) { for (const [procedureName, procedure] of Object.entries(procedures)) { if (procedure.input) { if (this.isSimpleSchemaReference(procedure.input)) { continue; } const [inputDefinition, , inputDependencies] = this.getZodDefinitionStringIterative(procedure.input, routerName, procedureName, true); if (inputDependencies.length > 0 || this.isComplexDefinition(inputDefinition)) { const inputSchemaName = procedure.input.description ? procedure.input.description : this.generateSchemaName(routerName, procedureName, 'Input'); this.queueSchemaForProcessing(procedure.input, inputSchemaName, routerName, procedureName, true); } } if (procedure.output) { if (this.isSimpleSchemaReference(procedure.output)) { continue; } const [outputDefinition, , outputDependencies] = this.getZodDefinitionStringIterative(procedure.output, routerName, procedureName, true); if (outputDependencies.length > 0 || this.isComplexDefinition(outputDefinition)) { const outputSchemaName = procedure.output.description ? procedure.output.description : this.generateSchemaName(routerName, procedureName, 'Output'); this.queueSchemaForProcessing(procedure.output, outputSchemaName, routerName, procedureName, true); } } } this.processSchemaQueue(); } isSimpleSchemaReference(schema) { if (!schema || !schema.def) { return false; } const typeName = schema.def.type; switch (typeName) { case 'nullable': { const typedSchema = schema; const innerType = typedSchema.def.innerType; return !!innerType.description && innerType.def.type !== 'object'; } case 'optional': { const typedSchema = schema; const innerType = typedSchema.def.innerType; return !!innerType.description && innerType.def.type !== 'object'; } case 'array': { const typedSchema = schema; const elementType = typedSchema.def.element; return !!elementType.description && elementType.def.type !== 'object'; } default: return !!schema.description && schema.def.type !== 'object'; } } isComplexDefinition(definition) { return (definition.startsWith('z.object(') || definition.startsWith('z.union(') || definition.startsWith('z.intersection(') || definition.startsWith('z.array(z.object(') || definition.startsWith('z.record(') || definition.startsWith('z.tuple(') || definition.includes('z.object({')); } queueSchemaForProcessing(schema, schemaName, routerName, procedureName, firstIteration = false) { if (!this.schemaRegistry.has(schemaName) && !this.isSchemaInQueue(schema, schemaName)) { this.schemaProcessingQueue.push({ schema, schemaName, routerName, procedureName, firstIteration, }); } } isSchemaInQueue(schema, schemaName) { return this.schemaProcessingQueue.some((item) => item.schema === schema && item.schemaName === schemaName); } processSchemaQueue() { let iterations = 0; const maxIterations = 1000; while (this.schemaProcessingQueue.length > 0 && iterations < maxIterations) { iterations++; const currentBatch = [...this.schemaProcessingQueue]; this.schemaProcessingQueue = []; for (const { schema, schemaName, routerName, procedureName, firstIteration } of currentBatch) { try { this.processSingleSchema(schema, schemaName, routerName, procedureName, firstIteration); } catch (error) { error_handler_1.ErrorHandler.logWarning('SchemaGenerator', `Error processing schema ${schemaName}`, error); } } } if (iterations >= maxIterations) { error_handler_1.ErrorHandler.logWarning('SchemaGenerator', 'Schema processing stopped due to iteration limit - possible infinite loop detected'); } } processSingleSchema(schema, schemaName, routerName, procedureName, firstIteration) { if (this.schemaRegistry.has(schemaName)) { return; } try { const [schemaDefinition, typeDefinition, dependencies] = this.getZodDefinitionStringIterative(schema, routerName, procedureName, firstIteration); this.schemaRegistry.set(schemaName, { name: schemaName, definition: schemaDefinition, typeName: `${schemaName}Type`, typeDefinition, dependencies, schema: schema, }); } catch (error) { error_handler_1.ErrorHandler.logWarning('SchemaGenerator', `Failed to process schema ${schemaName}`, error); this.schemaRegistry.set(schemaName, { name: schemaName, definition: 'z.unknown()', typeName: `${schemaName}Type`, typeDefinition: 'unknown', dependencies: [], schema: schema, }); } } isZodObject(schema) { return schema && schema.def.type === 'object'; } generateSchemaName(routerName, procedureName, type) { const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); const sanitize = (str) => str.replace(/[^a-zA-Z0-9]/g, ''); const routerPart = capitalize(sanitize(routerName)); const procedurePart = capitalize(sanitize(procedureName)); return `${routerPart}${procedurePart}${type}Schema`; } expandZodObjectInline(schema, routerName, procedureName) { try { if (!schema.def.shape) { return ['z.unknown()', 'z.ZodUnknown', []]; } const shape = schema.def.shape; if (typeof shape !== 'object' || shape === null) { return ['z.unknown()', 'z.ZodUnknown', []]; } const schemaFields = Object.entries(shape) .map(([key, fieldSchema]) => { const [definition, typeName, dependencies] = this.getZodDefinitionStringIterative(fieldSchema, routerName, procedureName); if (dependencies.length > 0) { return `get ${key}(): ${typeName} { return ${definition} as ${typeName} },`; } return `${key}: ${definition},`; }) .join('\n'); const typeNames = Object.entries(shape) .map(([key, fieldSchema]) => { const [definition, typeName, dependencies] = this.getZodDefinitionStringIterative(fieldSchema, routerName, procedureName); if (typeName.includes('| null') || typeName.includes('| undefined')) { key = `${key}?`; } return ` ${key}: ${typeName};`; }) .join('\n'); const dependencies = Object.entries(shape) .map(([key, fieldSchema]) => { const [definition, typeName, dependencies] = this.getZodDefinitionStringIterative(fieldSchema, routerName, procedureName); return dependencies; }) .flat(); let objectConfigSuffix = ''; if (schema.def.catchall && schema.def.catchall.def.type !== 'never') { const catchallSchema = schema.def.catchall; const [catchallDef] = this.getZodDefinitionStringIterative(catchallSchema, routerName, procedureName); objectConfigSuffix = `.catchall(${catchallDef})`; } let objectDescription = ''; if (schema.description) { objectDescription = `.describe("${schema.description}")`; } return [`z.object({\n${schemaFields}\n})${objectConfigSuffix}${objectDescription}`, `{\n${typeNames}\n}`, dependencies]; } catch (error) { error_handler_1.ErrorHandler.logWarning('SchemaGenerator', 'Failed to expand object inline', error); return ['z.unknown()', 'z.ZodUnknown', []]; } } getZodDefinitionStringIterative(schema, routerName, procedureName, firstIteration = false) { var _a, _b; if (!schema || !schema.def) { return ['z.unknown()', 'z.ZodUnknown', []]; } const typeName = schema.def.type; switch (typeName) { case 'string': return ['z.string()', 'z.ZodString', []]; case 'number': return ['z.number()', 'z.ZodNumber', []]; case 'boolean': return ['z.boolean()', 'z.ZodBoolean', []]; case 'date': return ['z.date()', 'z.ZodDate', []]; case 'bigint': return ['z.bigint()', 'z.ZodBigInt', []]; case 'any': return ['z.any()', 'z.ZodAny', []]; case 'unknown': return ['z.unknown()', 'z.ZodUnknown', []]; case 'void': return ['z.void()', 'z.ZodVoid', []]; case 'undefined': return ['z.undefined()', 'z.ZodUndefined', []]; case 'null': return ['z.null()', 'z.ZodNull', []]; case 'never': return ['z.never()', 'z.ZodNever', []]; case 'array': { const typedSchema = schema; const [elementType, elementTypeName, elementDependencies] = this.getZodDefinitionStringIterative(typedSchema.def.element, routerName, procedureName); let arrayTypeName = elementTypeName; if (elementTypeName.includes(' | ') || elementTypeName.includes(' & ')) { arrayTypeName = `(${elementTypeName})`; } return [`z.array(${elementType})`, `z.ZodArray<${arrayTypeName}>`, elementDependencies]; } case 'object': { const typedSchema = schema; if (firstIteration) { return this.expandZodObjectInline(typedSchema, routerName, procedureName); } const nestedSchemaName = this.generateNestedSchemaNameSafe(typedSchema); this.queueSchemaForProcessing(typedSchema, nestedSchemaName, routerName, procedureName, true); return [nestedSchemaName, `typeof ${nestedSchemaName}`, [nestedSchemaName]]; } case 'optional': { const typedSchema = schema; if (typedSchema.def.innerType.type === 'object' && firstIteration) { const innerObject = typedSchema.def.innerType; const [objectDef, objectType, deps] = this.expandZodObjectInline(innerObject, routerName, procedureName); return [`${objectDef}.optional()`, `z.ZodOptional<${objectType}>`, deps]; } const [innerType, innerTypeName, innerDependencies] = this.getZodDefinitionStringIterative(typedSchema.def.innerType, routerName, procedureName); return [`${innerType}.optional()`, `z.ZodOptional<${innerTypeName}>`, innerDependencies]; } case 'nullable': { const typedSchema = schema; if (typedSchema.def.innerType.def.type === 'object') { const innerObject = typedSchema.def.innerType; if (firstIteration) { const [objectDef, objectType, deps] = this.expandZodObjectInline(innerObject, routerName, procedureName); return [`${objectDef}.nullable()`, `z.ZodNullable<${objectType}>`, deps]; } } const [innerType, innerTypeName, innerDependencies] = this.getZodDefinitionStringIterative(typedSchema.def.innerType, routerName, procedureName); return [`${innerType}.nullable()`, `z.ZodNullable<${innerTypeName}>`, innerDependencies]; } case 'enum': { const typedSchema = schema; const schemaValues = typedSchema.options.map((v) => `"${v}"`).join(', '); const typeValues = typedSchema.options.map((v) => `${v}: "${v}"`).join(', '); return [`z.enum([${schemaValues}])`, `z.ZodEnum<{ ${typeValues} }>`, []]; } case 'union': { const typedSchema = schema; const definitions = typedSchema.def.options .map((opt) => this.getZodDefinitionStringIterative(opt, routerName, procedureName)[0]) .join(', '); const definitionTypes = typedSchema.def.options .map((opt) => { const typeName = this.getZodDefinitionStringIterative(opt, routerName, procedureName)[1]; return typeName; }) .join(', '); const dependencies = typedSchema.def.options .map((opt) => { const dependencies = this.getZodDefinitionStringIterative(opt, routerName, procedureName)[2]; return dependencies; }) .flat(); return [`z.union([${definitions}])`, `z.ZodUnion<[${definitionTypes}]>`, dependencies]; } case 'intersection': { const typedSchema = schema; const [leftType, leftTypeName, leftDependencies] = this.getZodDefinitionStringIterative(typedSchema.def.left, routerName, procedureName); const [rightType, rightTypeName, rightDependencies] = this.getZodDefinitionStringIterative(typedSchema.def.right, routerName, procedureName); return [ `z.intersection(${leftType}, ${rightType})`, `z.ZodIntersection<${leftTypeName}, ${rightTypeName}>`, [...leftDependencies, ...rightDependencies], ]; } case 'lazy': { const typedSchema = schema; try { const innerSchema = (_b = (_a = typedSchema.def).getter) === null || _b === void 0 ? void 0 : _b.call(_a); if (innerSchema && innerSchema !== schema) { const [innerType, innerTypeName, innerDependencies] = this.getZodDefinitionStringIterative(innerSchema, routerName, procedureName); return [`z.lazy(() => ${innerType})`, `z.ZodLazy<${innerTypeName}>`, innerDependencies]; } } catch (error) { error_handler_1.ErrorHandler.logWarning('SchemaGenerator', 'Error processing lazy schema', error); } return [`z.lazy(() => z.unknown())`, 'z.ZodLazy<z.ZodUnknown>', []]; } case 'record': { const typedSchema = schema; if (typedSchema.def.valueType) { const [valueType, valueTypeName, valueDependencies] = this.getZodDefinitionStringIterative(typedSchema.def.valueType, routerName, procedureName); return [`z.record(${valueType})`, `z.ZodRecord<string, ${valueTypeName}>`, valueDependencies]; } return [`z.record(z.unknown())`, 'z.ZodRecord<string, z.ZodUnknown>', []]; } case 'tuple': { const typedSchema = schema; if (typedSchema.def.items) { const definitions = typedSchema.def.items .map((item) => this.getZodDefinitionStringIterative(item, routerName, procedureName)[0]) .join(', '); const definitionTypes = typedSchema.def.items .map((item) => this.getZodDefinitionStringIterative(item, routerName, procedureName)[1]) .join(', '); const dependencies = typedSchema.def.items .map((item) => this.getZodDefinitionStringIterative(item, routerName, procedureName)[2]) .flat(); return [`z.tuple([${definitions}])`, `z.ZodTuple<[${definitionTypes}]>`, dependencies]; } return [`z.tuple([])`, 'z.ZodTuple<[]>[]', []]; } case 'literal': { const typedSchema = schema; const values = typedSchema.def.values; if (Array.isArray(values) && values.length > 0) { if (values.length === 1) { const value = values[0]; if (typeof value === 'string') { return [`z.literal("${value}")`, `z.ZodLiteral<"${value}">`, []]; } else if (typeof value === 'number' || typeof value === 'boolean') { return [`z.literal(${value})`, `z.ZodLiteral<${value}>`, []]; } else if (typeof value === 'bigint') { return [`z.literal(${value}n)`, `z.ZodLiteral<${value}n>`, []]; } else if (value === null) { return [`z.literal(null)`, `z.ZodLiteral<null>`, []]; } else if (value === undefined) { return [`z.literal(undefined)`, `z.ZodLiteral<undefined>`, []]; } return [`z.literal(${JSON.stringify(value)})`, `z.ZodLiteral<${JSON.stringify(value)}>`, []]; } else { const literalDefs = values.map((value) => { if (typeof value === 'string') { return `z.literal("${value}")`; } else if (typeof value === 'number' || typeof value === 'boolean') { return `z.literal(${value})`; } else if (typeof value === 'bigint') { return `z.literal(${value}n)`; } else if (value === null) { return `z.literal(null)`; } else if (value === undefined) { return `z.literal(undefined)`; } return `z.literal(${JSON.stringify(value)})`; }); const literalTypes = values.map((value) => { if (typeof value === 'string') { return `z.ZodLiteral<"${value}">`; } else if (typeof value === 'number' || typeof value === 'boolean') { return `z.ZodLiteral<${value}>`; } else if (typeof value === 'bigint') { return `z.ZodLiteral<${value}n>`; } else if (value === null) { return `z.ZodLiteral<null>`; } else if (value === undefined) { return `z.ZodLiteral<undefined>`; } return `z.ZodLiteral<${JSON.stringify(value)}>`; }); return [`z.union([${literalDefs.join(', ')}])`, `z.ZodUnion<[${literalTypes.join(', ')}]>`, []]; } } return [`z.literal(null)`, `z.ZodLiteral<null>`, []]; } default: error_handler_1.ErrorHandler.logWarning('SchemaGenerator', `Unknown schema type: ${typeName}`); return ['z.unknown()', 'z.ZodUnknown', []]; } } generateNestedSchemaNameSafe(schema) { if (this.schemaNameCache.has(schema)) { return this.schemaNameCache.get(schema); } const description = schema.description; if (description) { const isNameValid = /^[a-zA-Z0-9_]+$/.test(description) && description.length > 0; if (isNameValid) { this.schemaNameCache.set(schema, description); return description; } } const structureHash = this.createSchemaHashSafe(schema); const finalName = `Schema${structureHash}`; let counter = 1; let uniqueName = finalName; while (this.schemaRegistry.has(uniqueName)) { uniqueName = `${finalName}${counter}`; counter++; } this.schemaNameCache.set(schema, uniqueName); return uniqueName; } generateNestedSchemaNameSafeForRouter(schema) { if (this.schemaNameCache.has(schema)) { return this.schemaNameCache.get(schema); } const description = schema.description; if (description) { this.schemaNameCache.set(schema, description); return description; } const structureHash = this.createSchemaHashSafe(schema); const finalName = `Schema${structureHash}`; let counter = 1; let uniqueName = finalName; while (this.schemaRegistry.has(uniqueName)) { uniqueName = `${finalName}${counter}`; counter++; } this.schemaNameCache.set(schema, uniqueName); return uniqueName; } createSchemaHashSafe(schema) { if (this.schemaHashCache.has(schema)) { return this.schemaHashCache.get(schema); } try { const typeName = schema.def.type || 'unknown'; let hashInput = typeName.toString(); try { const shape = schema.def.shape; if (shape && typeof shape === 'object') { const keys = Object.keys(shape).sort().slice(0, 10); hashInput += '_' + keys.join('_'); } } catch (error) { error_handler_1.ErrorHandler.logWarning('SchemaGenerator', 'Could not get schema shape for hash', error); } let hash = 0; for (let i = 0; i < hashInput.length; i++) { const char = hashInput.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } const result = Math.abs(hash).toString(36).toUpperCase(); this.schemaHashCache.set(schema, result); return result; } catch (error) { error_handler_1.ErrorHandler.logWarning('SchemaGenerator', 'Error creating schema hash', error); const result = 'Unknown'; this.schemaHashCache.set(schema, result); return result; } } topologicalSort() { const allSchemas = Array.from(this.schemaRegistry.keys()); const result = []; const visited = new Set(); const visiting = new Set(); const visit = (schemaName) => { if (visited.has(schemaName)) { return; } if (visiting.has(schemaName)) { visited.add(schemaName); result.push(schemaName); return; } visiting.add(schemaName); const schemaInfo = this.schemaRegistry.get(schemaName); if (schemaInfo === null || schemaInfo === void 0 ? void 0 : schemaInfo.dependencies) { const allDeps = [...new Set(schemaInfo.dependencies)]; const uniqueDeps = allDeps.filter((dep) => dep !== schemaName && this.schemaRegistry.has(dep)); for (const dep of uniqueDeps) { visit(dep); } } visiting.delete(schemaName); visited.add(schemaName); result.push(schemaName); }; for (const schemaName of allSchemas) { visit(schemaName); } return result; } getTransformationForm(schema) { if (!schema || !schema.def) { return null; } const typeName = schema.def.type; switch (typeName) { case 'nullable': { const typedSchema = schema; const innerType = typedSchema.def.innerType; if (innerType.description) { return `${innerType.description}.nullable()`; } return null; } case 'optional': { const typedSchema = schema; const innerType = typedSchema.def.innerType; if (innerType.description) { return `${innerType.description}.optional()`; } return null; } case 'array': { const typedSchema = schema; const elementType = typedSchema.def.element; if (elementType.description) { return `z.array(${elementType.description})`; } return null; } default: if (schema.description) { return schema.description; } return null; } } } exports.SchemaGenerator = SchemaGenerator; //# sourceMappingURL=schema-generator.js.map