typescript-runtime-schemas
Version:
A TypeScript schema generation tool that extracts Zod schemas from TypeScript source files with runtime validation support. Generate validation schemas directly from your existing TypeScript types with support for computed types and constraint-based valid
328 lines • 11.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaFileWriter = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/**
* SchemaFileWriter - Handles writing Zod schemas to disk
*/
class SchemaFileWriter {
/**
* Write schemas to disk based on output options
*/
static async writeSchemas(schemas, options = {}, inputDir) {
const outputDir = options.outputDir || 'generated';
const preserveStructure = options.preserveStructure || false;
// Ensure output directory exists
await this.ensureDirectoryExists(outputDir);
if (preserveStructure) {
return this.writeStructuredSchemas(schemas, outputDir, inputDir);
}
else {
return this.writeSingleSchemaFile(schemas, outputDir);
}
}
/**
* Write all schemas to a single schemas.zod.ts file
*/
static async writeSingleSchemaFile(schemas, outputDir) {
const outputPath = path.join(outputDir, 'schemas.zod.ts');
const content = this.generateSingleFileContent(schemas);
await fs.promises.writeFile(outputPath, content, 'utf-8');
return [outputPath];
}
/**
* Write schemas preserving directory structure
*/
static async writeStructuredSchemas(schemas, outputDir, inputDir) {
// Group schemas by source file
const schemasByFile = this.groupSchemasBySourceFile(schemas);
const writtenFiles = [];
for (const [sourceFile, fileSchemas] of Object.entries(schemasByFile)) {
const outputPath = this.getStructuredOutputPath(sourceFile, outputDir, inputDir);
// Ensure the directory exists
await this.ensureDirectoryExists(path.dirname(outputPath));
const content = this.generateFileContent(fileSchemas, sourceFile);
await fs.promises.writeFile(outputPath, content, 'utf-8');
writtenFiles.push(outputPath);
}
return writtenFiles;
}
/**
* Group schemas by their source file
*/
static groupSchemasBySourceFile(schemas) {
const grouped = {};
for (const schema of schemas) {
if (schema.sourceInfo?.filePath) {
const filePath = schema.sourceInfo.filePath;
if (!grouped[filePath]) {
grouped[filePath] = [];
}
grouped[filePath].push(schema);
}
else {
// Handle schemas without source info
const unknownFile = 'unknown.ts';
if (!grouped[unknownFile]) {
grouped[unknownFile] = [];
}
grouped[unknownFile].push(schema);
}
}
return grouped;
}
/**
* Get the output path for a structured file
*/
static getStructuredOutputPath(sourceFilePath, outputDir, inputDir) {
// Get the base name without extension
const baseName = path.basename(sourceFilePath, path.extname(sourceFilePath));
// Try to preserve directory structure relative to current working directory
const relativePath = path.relative(process.cwd(), sourceFilePath);
let relativeDir = path.dirname(relativePath);
// If we have an input directory, strip it from the relative path
if (inputDir) {
const inputRelative = path.relative(process.cwd(), inputDir);
if (relativeDir.startsWith(inputRelative)) {
// Remove the input directory from the path
relativeDir = path.relative(inputRelative, relativeDir);
}
}
// Create output filename
const outputFileName = `${baseName}.zod.ts`;
// If the relative directory is meaningful (not just '.'), preserve it
if (relativeDir && relativeDir !== '.' && !relativeDir.startsWith('..')) {
return path.join(outputDir, relativeDir, outputFileName);
}
else {
return path.join(outputDir, outputFileName);
}
}
/**
* Generate content for a single consolidated file
*/
static generateSingleFileContent(schemas) {
const lines = [];
// Add import
lines.push("import { z } from 'zod';");
lines.push('');
// Group schemas by source file for organization
const schemasByFile = this.groupSchemasBySourceFile(schemas);
for (const [sourceFile, fileSchemas] of Object.entries(schemasByFile)) {
if (sourceFile !== 'unknown.ts') {
lines.push(`// From ${sourceFile}`);
}
for (const schema of fileSchemas) {
lines.push(this.generateSchemaExport(schema));
lines.push('');
}
}
return lines.join('\n');
}
/**
* Generate content for a single file
*/
static generateFileContent(schemas, sourceFile) {
const lines = [];
// Add import
lines.push("import { z } from 'zod';");
lines.push('');
if (sourceFile && sourceFile !== 'unknown.ts') {
lines.push(`// Generated from ${sourceFile}`);
lines.push('');
}
for (const schema of schemas) {
lines.push(this.generateSchemaExport(schema));
lines.push('');
}
return lines.join('\n');
}
/**
* Generate the export statement for a schema
*/
static generateSchemaExport(schema) {
const zodSchemaString = this.zodSchemaToString(schema.zodSchema);
return `export const ${schema.typeName} = ${zodSchemaString};`;
}
/**
* Convert a Zod schema to its string representation
*/
static zodSchemaToString(zodSchema) {
// This is a simplified version - we'll need to implement proper serialization
// For now, we'll use a basic approach that handles common cases
if (zodSchema._def) {
return this.serializeZodSchema(zodSchema);
}
// Fallback
return 'z.unknown()';
}
/**
* Serialize a Zod schema to string representation
*/
static serializeZodSchema(schema) {
const def = schema._def;
switch (def.typeName) {
case 'ZodObject':
return this.serializeZodObject(schema);
case 'ZodString':
return this.serializeZodString(schema);
case 'ZodNumber':
return this.serializeZodNumber(schema);
case 'ZodBoolean':
return 'z.boolean()';
case 'ZodArray':
return this.serializeZodArray(schema);
case 'ZodOptional':
return `${this.serializeZodSchema(def.innerType)}.optional()`;
case 'ZodUnknown':
return 'z.unknown()';
default:
console.warn(`Unsupported Zod type: ${def.typeName}`);
return 'z.unknown()';
}
}
/**
* Serialize a ZodObject
*/
static serializeZodObject(schema) {
const shape = schema._def.shape();
const properties = [];
for (const [key, value] of Object.entries(shape)) {
const serializedValue = this.serializeZodSchema(value);
properties.push(` ${key}: ${serializedValue}`);
}
if (properties.length === 0) {
return 'z.object({})';
}
return `z.object({\n${properties.join(',\n')}\n})`;
}
/**
* Serialize a ZodString with constraints
*/
static serializeZodString(schema) {
let result = 'z.string()';
const checks = schema._def.checks || [];
for (const check of checks) {
switch (check.kind) {
case 'min':
result += `.min(${check.value})`;
break;
case 'max':
result += `.max(${check.value})`;
break;
case 'email':
result += '.email()';
break;
case 'uuid':
result += '.uuid()';
break;
case 'url':
result += '.url()';
break;
case 'regex':
result += `.regex(${check.regex})`;
break;
case 'datetime':
result += '.datetime()';
break;
case 'ip':
result += '.ip()';
break;
}
}
return result;
}
/**
* Serialize a ZodNumber with constraints
*/
static serializeZodNumber(schema) {
let result = 'z.number()';
const checks = schema._def.checks || [];
for (const check of checks) {
switch (check.kind) {
case 'min':
result += `.min(${check.value})`;
break;
case 'max':
result += `.max(${check.value})`;
break;
case 'int':
result += '.int()';
break;
case 'positive':
result += '.positive()';
break;
case 'negative':
result += '.negative()';
break;
}
}
return result;
}
/**
* Serialize a ZodArray
*/
static serializeZodArray(schema) {
const elementType = this.serializeZodSchema(schema._def.type);
let result = `z.array(${elementType})`;
const checks = schema._def.checks || [];
for (const check of checks) {
switch (check.kind) {
case 'min':
result += `.min(${check.value})`;
break;
case 'max':
result += `.max(${check.value})`;
break;
}
}
return result;
}
/**
* Ensure a directory exists, creating it if necessary
*/
static async ensureDirectoryExists(dirPath) {
try {
await fs.promises.access(dirPath);
}
catch (error) {
await fs.promises.mkdir(dirPath, { recursive: true });
}
}
}
exports.SchemaFileWriter = SchemaFileWriter;
//# sourceMappingURL=schema-file-writer.js.map