@nexica/nestjs-trpc
Version:
NestJS TRPC Bridge
600 lines • 28.2 kB
JavaScript
"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