@docyrus/tanstack-db-generator
Version:
Code generator utilities for TanStack Query / Database integration with Docyrus API
227 lines (223 loc) • 8.32 kB
JavaScript
;
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()';
}
}