UNPKG

@docyrus/tanstack-db-generator

Version:

Code generator utilities for TanStack Query / Database integration with Docyrus API

227 lines (223 loc) 8.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateTypes = generateTypes; const fs_1 = require("fs"); const path_1 = require("path"); async function generateTypes(spec, dataSources, outputDir) { const typeDefinitions = []; const zodSchemas = []; // Create a set of dataSource entity names for quick lookup const dataSourceEntityNames = new Set(dataSources.map(ds => ds.entityName)); // Generate ALL types from components.schemas if (spec.components?.schemas) { for (const [schemaName, schema] of Object.entries(spec.components.schemas)) { const typeDef = generateTypeFromSchema(schemaName, schema, spec); typeDefinitions.push(typeDef); // Generate Zod schema const isDataSourceEntity = dataSourceEntityNames.has(schemaName); const zodSchema = generateZodSchema(schemaName, schema, spec, isDataSourceEntity); zodSchemas.push(zodSchema); } } // Write types file const content = generateTypesFile(typeDefinitions, zodSchemas); (0, fs_1.writeFileSync)((0, path_1.join)(outputDir, 'index.ts'), content); } function findEntitySchema(spec, entityName) { if (!spec.components?.schemas) return null; // Try exact match if (spec.components.schemas[entityName]) { return spec.components.schemas[entityName]; } // Try to find by similar names (e.g., BaseContactEntity) for (const [schemaName, schema] of Object.entries(spec.components.schemas)) { if (schemaName.toLowerCase().includes(entityName.toLowerCase().replace('entity', ''))) { return schema; } } return null; } function generateTypeFromSchema(name, schema, spec) { const properties = resolveSchemaProperties(schema, spec); const fields = []; const propertyInfo = []; for (const [propName, propSchema] of Object.entries(properties)) { const resolved = resolveSchema(propSchema, spec); const type = getTypeScriptType(resolved, spec); const isOptional = !schema.required?.includes(propName) || !!resolved.readOnly; fields.push(` ${propName}${isOptional ? '?' : ''}: ${type};`); propertyInfo.push({ name: propName, type, isOptional, schema: resolved }); } // Store property info for Zod schema generation schema._propertyInfo = propertyInfo; return `export interface ${name} { ${fields.join('\n')} }`; } function resolveSchemaProperties(schema, spec) { let properties = {}; // Handle allOf if (schema.allOf) { for (const subSchema of schema.allOf) { const resolved = resolveSchema(subSchema, spec); if (resolved.properties) { const resolvedProps = {}; for (const [key, value] of Object.entries(resolved.properties)) { resolvedProps[key] = resolveSchema(value, spec); } properties = { ...properties, ...resolvedProps }; } } } // Add direct properties if (schema.properties) { const resolvedProps = {}; for (const [key, value] of Object.entries(schema.properties)) { resolvedProps[key] = resolveSchema(value, spec); } properties = { ...properties, ...resolvedProps }; } return properties; } function resolveSchema(schema, spec) { if (schema.$ref) { const ref = schema.$ref; const path = ref.replace('#/', '').split('/'); let current = spec; for (const segment of path) { current = current[segment]; if (!current) break; } return current || {}; } return schema; } function getTypeScriptType(schema, spec) { if (schema.oneOf) { // Handle oneOf for relations const types = schema.oneOf .map(s => getTypeScriptType(resolveSchema(s, spec || {}), spec)) .filter((type, index, self) => self.indexOf(type) === index); // Remove duplicates return types.join(' | '); } switch (schema.type) { case 'string': if (schema.enum) { return schema.enum.map(v => `'${v}'`).join(' | '); } if (schema.format === 'uuid') return 'string'; if (schema.format === 'date-time') return 'string'; return 'string'; case 'number': case 'integer': return 'number'; case 'boolean': return 'boolean'; case 'array': if (schema.items) { return `Array<${getTypeScriptType(schema.items, spec)}>`; } return 'Array<any>'; case 'object': if (schema.properties) { const props = Object.entries(schema.properties) .map(([key, value]) => `${key}: ${getTypeScriptType(value, spec)}`) .join('; '); return `{ ${props} }`; } return 'Record<string, any>'; default: return 'any'; } } function generateTypesFile(typeDefinitions, zodSchemas) { return `// Generated types from OpenAPI specification // DO NOT EDIT - This file is auto-generated import { z } from 'zod'; ${typeDefinitions.join('\n\n')} // Zod schemas for validation ${zodSchemas.join('\n\n')} `; } function generateZodSchema(entityName, schema, spec, isDataSourceEntity) { const properties = resolveSchemaProperties(schema, spec); const zodFields = []; for (const [propName, propSchema] of Object.entries(properties)) { const resolved = resolveSchema(propSchema, spec); const zodType = getZodType(resolved, spec); const isOptional = !schema.required?.includes(propName) || !!resolved.readOnly; const field = ` ${propName}: ${zodType}${isOptional ? '.optional()' : ''}`; zodFields.push(field); } const baseName = entityName.replace('Entity', ''); let result = `export const ${entityName}Schema = z.object({ ${zodFields.join(',\n')} });`; // Only generate Create and Update schemas for dataSource entities if (isDataSourceEntity) { result += `\n\nexport const ${baseName}CreateSchema = ${entityName}Schema.omit({ id: true, created_on: true, created_by: true, last_modified_on: true, last_modified_by: true }); export const ${baseName}UpdateSchema = ${entityName}Schema.partial().omit({ id: true, created_on: true, created_by: true, last_modified_on: true, last_modified_by: true });`; } return result; } function getZodType(schema, spec) { if (schema.oneOf) { // Handle oneOf for relations const types = schema.oneOf .map(s => getZodType(resolveSchema(s, spec || {}), spec)) .filter((type, index, self) => self.indexOf(type) === index); return types.length > 1 ? `z.union([${types.join(', ')}])` : types[0]; } switch (schema.type) { case 'string': if (schema.enum) { const literals = schema.enum.map(v => `z.literal('${v}')`).join(', '); return `z.union([${literals}])`; } if (schema.format === 'uuid') return 'z.uuid()'; if (schema.format === 'date-time') return 'z.iso.datetime()'; if (schema.format === 'email') return 'z.string().email()'; if (schema.format === 'uri') return 'z.string().url()'; return 'z.string()'; case 'number': case 'integer': return 'z.number()'; case 'boolean': return 'z.boolean()'; case 'array': if (schema.items) { return `z.array(${getZodType(schema.items, spec)})`; } return 'z.array(z.any())'; case 'object': if (schema.properties) { const props = Object.entries(schema.properties) .map(([key, value]) => `${key}: ${getZodType(value, spec)}`) .join(', '); return `z.object({ ${props} })`; } return 'z.record(z.string(), z.any())'; default: return 'z.any()'; } }