moduleSelection.ts•13.1 kB
/**
* Dedicated Zod schemas for Module Selection in Fullstack Starter Kit Generator
*
* These schemas define the exact structure expected from LLM responses
* for module selection and template generation, enabling precise validation
* and better prompt engineering.
*/
import { z } from 'zod';
/**
* Schema for module parameters used in template substitution
*/
export const moduleParametersSchema = z.record(z.union([
z.string(),
z.number(),
z.boolean()
])).describe('Key-value pairs for template parameter substitution');
/**
* Schema for individual module selection
*/
export const moduleSelectionItemSchema = z.object({
modulePath: z.string()
.min(1, 'Module path cannot be empty')
.describe('Path to the module template (e.g., "frontend/react-vite", "backend/nodejs-express")'),
params: moduleParametersSchema
.describe('Parameters for template substitution specific to this module'),
moduleKey: z.string()
.optional()
.describe('Optional key for path resolution (e.g., "frontend", "backend")')
}).describe('Configuration for a single module to be included in the starter kit');
/**
* Enhanced module selection item schema with module type support
*/
export const enhancedModuleSelectionItemSchema = z.object({
modulePath: z.string()
.min(1, 'Module path cannot be empty')
.describe('Path to the module template (e.g., "ai-integration/openai-langchain", "development-tools/monaco-judge0")'),
moduleType: z.string()
.min(1, 'Module type cannot be empty')
.describe('Type/category of the module (e.g., "ai-integration", "development-tools", "real-time", "payment")'),
params: moduleParametersSchema
.describe('Parameters for template substitution specific to this module'),
moduleKey: z.string()
.optional()
.describe('Optional key for path resolution (e.g., "frontend", "backend", "root")')
}).describe('Enhanced configuration for a single module with type classification');
/**
* Schema for global parameters that apply to the entire project
*/
export const globalParametersSchema = z.object({
projectName: z.string()
.min(1, 'Project name is required')
.max(100, 'Project name must be 100 characters or less')
.regex(/^[a-zA-Z0-9\s\-_]+$/, 'Project name can only contain letters, numbers, spaces, hyphens, and underscores')
.describe('The name of the project being generated'),
projectDescription: z.string()
.min(10, 'Project description must be at least 10 characters')
.max(500, 'Project description must be 500 characters or less')
.describe('A brief description of what the project does'),
frontendPath: z.string()
.describe('Directory path for frontend code (default: "client")'),
backendPath: z.string()
.describe('Directory path for backend code (default: "server")'),
backendPort: z.number()
.int()
.min(1000, 'Port must be at least 1000')
.max(65535, 'Port must be at most 65535')
.describe('Port number for the backend server (default: 3000)'),
databaseName: z.string()
.optional()
.describe('Name of the database (if applicable)'),
apiPrefix: z.string()
.describe('API route prefix (default: "/api")'),
enableTypeScript: z.boolean()
.describe('Whether to use TypeScript (default: true)'),
enableTesting: z.boolean()
.describe('Whether to include testing setup (default: true)'),
enableDocker: z.boolean()
.describe('Whether to include Docker configuration (default: false)'),
enableCICD: z.boolean()
.describe('Whether to include CI/CD configuration (default: false)')
}).describe('Global parameters that apply to the entire project configuration');
/**
* Main schema for module selection response from LLM
*/
export const moduleSelectionResponseSchema = z.object({
globalParams: globalParametersSchema
.describe('Global parameters that apply to the entire project'),
moduleSelections: z.array(moduleSelectionItemSchema)
.min(1, 'At least one module must be selected')
.max(10, 'Cannot select more than 10 modules')
.describe('Array of modules to be included in the starter kit')
}).describe('Complete module selection configuration for generating a fullstack starter kit');
/**
* Schema for dynamic template generation response
* Updated to match the actual structure expected by the system and generated by LLMs
*/
export const dynamicTemplateSchema = z.object({
moduleName: z.string()
.min(1, 'Module name is required')
.describe('Unique identifier for this module'),
description: z.string()
.min(10, 'Description must be at least 10 characters')
.describe('Brief description of what this module provides'),
type: z.string()
.min(1, 'Module type is required')
.describe('Category/type of the module (e.g., frontend, backend, database, fullstack, utility, monitoring, etc.)'),
placeholders: z.array(z.string())
.optional()
.describe('List of placeholder variables used in this template'),
provides: z.object({
techStack: z.record(z.object({
name: z.string().describe('Technology name'),
version: z.string().optional().describe('Version specification'),
rationale: z.string().optional().describe('Why this technology was chosen')
})).optional().describe('Technologies provided by this module'),
directoryStructure: z.array(z.object({
path: z.string().min(1, "Path cannot be empty").describe('File or directory path'),
type: z.enum(['file', 'directory']).describe('Whether this is a file or directory'),
content: z.string().nullable().describe('File content (null for directories, string for files)'),
generationPrompt: z.string().nullable().optional().describe('Prompt for generating file content'),
children: z.array(z.any()).optional().describe('Child items (for directories only)')
})).optional().describe('Directory structure created by this module'),
dependencies: z.object({
npm: z.record(z.object({
dependencies: z.record(z.string()).optional(),
devDependencies: z.record(z.string()).optional()
})).optional()
}).optional().describe('Package dependencies added by this module'),
setupCommands: z.array(z.object({
command: z.string().describe('Command to execute'),
context: z.string().optional().describe('Context or explanation for the command')
})).optional().describe('Commands to run during setup with context'),
nextSteps: z.array(z.string())
.optional()
.describe('Recommended next steps after using this module')
}).describe('What this module provides to the project')
}).describe('Dynamically generated module template structure');
/**
* Enhanced module selection response schema for research-driven dynamic generation
* Supports more complex projects with increased module limits and type classification
*/
export const enhancedModuleSelectionResponseSchema = z.object({
globalParams: globalParametersSchema
.describe('Global parameters that apply to the entire project'),
moduleSelections: z.array(enhancedModuleSelectionItemSchema)
.min(1, 'At least one module must be selected')
.max(15, 'Maximum 15 modules allowed for complex projects')
.describe('Array of selected modules with their configurations and types')
}).describe('Enhanced response schema for complex module selection with research context');
/**
* Unified template schema for dynamic template generation
* Supports comprehensive module definitions with research-enhanced context
*/
export const unifiedTemplateSchema = z.object({
moduleName: z.string()
.min(1, 'Module name is required')
.describe('Unique identifier for this module'),
description: z.string()
.min(10, 'Description must be at least 10 characters')
.describe('Brief description of what this module provides'),
type: z.string()
.min(1, 'Module type is required')
.describe('Category/type of the module (e.g., ai-integration, development-tools, real-time)'),
provides: z.object({
techStack: z.record(z.object({
name: z.string().describe('Technology name'),
version: z.string().optional().describe('Version specification'),
rationale: z.string().describe('Why this technology was chosen based on research')
})).optional().describe('Technologies provided by this module'),
directoryStructure: z.array(z.object({
path: z.string().min(1, "Path cannot be empty").describe('File or directory path'),
type: z.enum(['file', 'directory']).describe('Whether this is a file or directory'),
content: z.string().nullable().describe('File content (null for directories)'),
generationPrompt: z.string().nullable().optional().describe('Prompt for generating file content'),
children: z.array(z.any()).optional().describe('Child items (for directories only)')
})).optional().describe('Directory structure created by this module'),
setupCommands: z.array(z.object({
command: z.string().describe('Command to execute'),
context: z.string().optional().describe('Context or explanation for the command')
})).optional().describe('Commands to run during setup with context')
}).describe('What this module provides to the project')
}).describe('Unified template schema for research-enhanced dynamic generation');
// Export TypeScript types for use in the application
export type ModuleParameters = z.infer<typeof moduleParametersSchema>;
export type ModuleSelectionItem = z.infer<typeof moduleSelectionItemSchema>;
export type EnhancedModuleSelectionItem = z.infer<typeof enhancedModuleSelectionItemSchema>;
export type GlobalParameters = z.infer<typeof globalParametersSchema>;
export type ModuleSelectionResponse = z.infer<typeof moduleSelectionResponseSchema>;
export type EnhancedModuleSelectionResponse = z.infer<typeof enhancedModuleSelectionResponseSchema>;
export type DynamicTemplate = z.infer<typeof dynamicTemplateSchema>;
export type UnifiedTemplate = z.infer<typeof unifiedTemplateSchema>;
/**
* Validation helper functions
*/
export function validateModuleSelectionResponse(data: unknown): data is ModuleSelectionResponse {
try {
moduleSelectionResponseSchema.parse(data);
return true;
} catch {
return false;
}
}
export function validateDynamicTemplate(data: unknown): data is DynamicTemplate {
try {
dynamicTemplateSchema.parse(data);
return true;
} catch {
return false;
}
}
export function validateEnhancedModuleSelectionResponse(data: unknown): data is EnhancedModuleSelectionResponse {
try {
enhancedModuleSelectionResponseSchema.parse(data);
return true;
} catch {
return false;
}
}
export function validateUnifiedTemplate(data: unknown): data is UnifiedTemplate {
try {
unifiedTemplateSchema.parse(data);
return true;
} catch {
return false;
}
}
/**
* Schema validation with detailed error messages
*/
export function validateModuleSelectionWithErrors(data: unknown): {
success: boolean;
data?: ModuleSelectionResponse;
errors?: string[];
} {
try {
const validated = moduleSelectionResponseSchema.parse(data);
return { success: true, data: validated };
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.issues.map(issue =>
`${issue.path.join('.')}: ${issue.message}`
);
return { success: false, errors };
}
return { success: false, errors: ['Unknown validation error'] };
}
}
export function validateDynamicTemplateWithErrors(data: unknown): {
success: boolean;
data?: DynamicTemplate;
errors?: string[];
} {
try {
const validated = dynamicTemplateSchema.parse(data);
return { success: true, data: validated };
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.issues.map(issue =>
`${issue.path.join('.')}: ${issue.message}`
);
return { success: false, errors };
}
return { success: false, errors: ['Unknown validation error'] };
}
}
export function validateEnhancedModuleSelectionWithErrors(data: unknown): {
success: boolean;
data?: EnhancedModuleSelectionResponse;
errors?: string[];
} {
try {
const validated = enhancedModuleSelectionResponseSchema.parse(data);
return { success: true, data: validated };
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.issues.map(issue =>
`${issue.path.join('.')}: ${issue.message}`
);
return { success: false, errors };
}
return { success: false, errors: ['Unknown validation error'] };
}
}
export function validateUnifiedTemplateWithErrors(data: unknown): {
success: boolean;
data?: UnifiedTemplate;
errors?: string[];
} {
try {
const validated = unifiedTemplateSchema.parse(data);
return { success: true, data: validated };
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.issues.map(issue =>
`${issue.path.join('.')}: ${issue.message}`
);
return { success: false, errors };
}
return { success: false, errors: ['Unknown validation error'] };
}
}