UNPKG

@catalystlabs/awm

Version:

Appwrite Migration Tool - Schema management and code generation for Appwrite databases

249 lines (206 loc) • 7.78 kB
#!/usr/bin/env node import fs from 'fs/promises'; import path from 'path'; /** * AWM Zod Schema Generator * Generates Zod validation schemas from Appwrite schema */ class ZodGenerator { constructor(schemaPath = './appwrite.schema') { this.schemaPath = schemaPath; this.collections = []; } async parseSchema() { const content = await fs.readFile(this.schemaPath, 'utf-8'); const lines = content.split('\n'); let currentCollection = null; let inCollection = false; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Skip comments and empty lines if (!line || line.startsWith('//') || line.startsWith('/*')) continue; // Start of collection if (line.startsWith('collection ')) { const name = line.split(' ')[1].replace('{', '').trim(); currentCollection = { name, attributes: [], indexes: [], uniques: [] }; inCollection = true; continue; } // End of collection if (inCollection && line === '}') { this.collections.push(currentCollection); currentCollection = null; inCollection = false; continue; } // Parse attributes if (inCollection && currentCollection && !line.startsWith('@@')) { const attrMatch = line.match(/^(\w+)\s+(String|Int|Float|Boolean|DateTime)(\[\])?\s*(.*)/); if (attrMatch) { const [_, name, type, isArray, decorators] = attrMatch; const attribute = { name, type, isArray: !!isArray, required: decorators.includes('@required'), unique: decorators.includes('@unique'), default: this.extractDefault(decorators), size: this.extractSize(decorators), isRelationship: decorators.includes('@relationship') }; currentCollection.attributes.push(attribute); } } } } extractDefault(decorators) { const defaultMatch = decorators.match(/@default\((.*?)\)/); if (!defaultMatch) return null; const value = defaultMatch[1]; if (value === 'now') return 'now'; if (value === 'false') return false; if (value === 'true') return true; if (value === 'null') return null; if (!isNaN(value)) return Number(value); return value; } extractSize(decorators) { const sizeMatch = decorators.match(/@size\((\d+)\)/); return sizeMatch ? parseInt(sizeMatch[1]) : null; } mapToZodType(type, isArray, size, defaultValue) { let zodType; switch (type) { case 'String': zodType = 'z.string()'; if (size) zodType = `z.string().max(${size})`; break; case 'Int': zodType = 'z.number().int()'; break; case 'Float': zodType = 'z.number()'; break; case 'Boolean': zodType = 'z.boolean()'; break; case 'DateTime': zodType = 'z.union([z.string().datetime(), z.date()])'; break; default: zodType = 'z.any()'; } if (isArray) { zodType = `z.array(${zodType})`; } if (defaultValue !== null && defaultValue !== undefined) { if (defaultValue === 'now') { zodType += '.default(() => new Date())'; } else if (typeof defaultValue === 'string') { zodType += `.default('${defaultValue}')`; } else { zodType += `.default(${defaultValue})`; } } return zodType; } generateZodSchemas() { let output = `// Generated by AWM Zod Generator // DO NOT EDIT - This file is auto-generated from appwrite.schema import { z } from 'zod'; `; // Generate base document schema output += `// Base Appwrite document schema const BaseDocumentSchema = z.object({ $id: z.string().optional(), $createdAt: z.string().datetime().optional(), $updatedAt: z.string().datetime().optional(), $permissions: z.array(z.string()).optional(), $databaseId: z.string().optional(), $collectionId: z.string().optional(), }); `; // Generate schemas for each collection for (const collection of this.collections) { const schemaName = `${this.toPascalCase(collection.name)}Schema`; // Create schema output += `// ${collection.name} collection\n`; output += `export const ${schemaName} = BaseDocumentSchema.extend({\n`; for (const attr of collection.attributes) { if (attr.isRelationship) { // Relationships are just string IDs const zodType = attr.required ? 'z.string()' : 'z.string().optional()'; output += ` ${attr.name}: ${zodType}, // relationship\n`; } else { const zodType = this.mapToZodType(attr.type, attr.isArray, attr.size, attr.default); const optional = attr.required ? '' : '.optional()'; output += ` ${attr.name}: ${zodType}${optional},\n`; } } output += `});\n\n`; // Create type from schema output += `export type ${this.toPascalCase(collection.name)} = z.infer<typeof ${schemaName}>;\n\n`; // Create input schema (without auto-generated fields) output += `export const ${schemaName}Input = ${schemaName}.omit({\n`; output += ` $id: true,\n`; output += ` $createdAt: true,\n`; output += ` $updatedAt: true,\n`; output += ` $permissions: true,\n`; output += ` $databaseId: true,\n`; output += ` $collectionId: true,\n`; output += `});\n\n`; output += `export type ${this.toPascalCase(collection.name)}Input = z.infer<typeof ${schemaName}Input>;\n\n`; } // Generate validation helpers output += `// Validation helpers\n`; output += `export const validators = {\n`; for (const collection of this.collections) { const pascalCase = this.toPascalCase(collection.name); output += ` ${collection.name}: {\n`; output += ` parse: (data: unknown) => ${pascalCase}Schema.parse(data),\n`; output += ` safeParse: (data: unknown) => ${pascalCase}Schema.safeParse(data),\n`; output += ` parseInput: (data: unknown) => ${pascalCase}SchemaInput.parse(data),\n`; output += ` safeParseInput: (data: unknown) => ${pascalCase}SchemaInput.safeParse(data),\n`; output += ` },\n`; } output += `};\n`; return output; } toPascalCase(str) { return str.split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(''); } async generate(outputPath = './schemas/appwrite.schemas.ts') { console.log('šŸ” Parsing schema...'); await this.parseSchema(); console.log(`šŸ“ Found ${this.collections.length} collections`); const zodSchemas = this.generateZodSchemas(); // Ensure directory exists const dir = path.dirname(outputPath); await fs.mkdir(dir, { recursive: true }); // Write Zod schemas file await fs.writeFile(outputPath, zodSchemas); console.log(`āœ… Zod schemas generated: ${outputPath}`); return { collections: this.collections.length, outputPath }; } } // CLI usage if (import.meta.url === `file://${process.argv[1]}`) { const generator = new ZodGenerator(process.argv[2] || './appwrite.schema'); const outputPath = process.argv[3] || './schemas/appwrite.schemas.ts'; generator.generate(outputPath) .then(result => { console.log(`\n✨ Successfully generated Zod schemas for ${result.collections} collections`); }) .catch(error => { console.error('āŒ Error generating schemas:', error); process.exit(1); }); } export default ZodGenerator;