UNPKG

@adonisjs/lucid

Version:

SQL ORM built on top of Active Record pattern

122 lines (121 loc) 4.66 kB
/* * @adonisjs/lucid * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import merge from 'deepmerge'; import { ImportsBag } from '@poppinss/utils'; import stringHelpers from '@adonisjs/core/helpers/string'; import { DEFAULT_SCHEMA_RULES } from "./rules.js"; import { DATA_TYPES_MAPPING, INTERNAL_TYPES } from "./mappings.js"; /** * OrmSchemaBuilder handles the core logic of generating TypeScript * model schemas from database table structures. */ export class OrmSchemaBuilder { client; /** * Schema rules for type mapping and customization */ schema = DEFAULT_SCHEMA_RULES; /** * Mapping from database types to internal type identifiers */ dataTypes = DATA_TYPES_MAPPING; constructor(client) { this.client = client; } /** * Load user-defined schema rules */ loadRules(rules) { this.schema = merge.all([this.schema, ...rules]); } /** * Generate schema for a single column */ generateColumnSchema(columnName, column, tableName) { const dialectColumnType = `${this.client.dialect.name}.${column.type}`; // Map database type to internal type identifier // If no mapping exists, use the database type name itself const internalType = this.dataTypes[dialectColumnType] ?? this.dataTypes[column.type] ?? column.type; // For unknown internal types, allow users to define custom rules using the database type name const typeLookupKey = internalType === INTERNAL_TYPES.UNKNOWN ? column.type : internalType; // Lookup hierarchy (most specific to least specific): // 1. Table-specific column // 2. Table-specific type // 3. Global column name // 4. Global type const ruleDef = this.schema.tables[tableName]?.columns?.[columnName] ?? this.schema.tables[tableName]?.types?.[typeLookupKey] ?? this.schema.columns[columnName] ?? this.schema.types[typeLookupKey]; const rule = typeof ruleDef === 'function' ? ruleDef(typeLookupKey) : ruleDef; // Default to 'any' type if no rule is defined const finalRule = rule ?? { tsType: 'any', imports: [], decorator: '@column()', }; let tsType = finalRule.tsType; let propertyName = stringHelpers.camelCase(columnName); if (column.nullable) { tsType += ' | null'; } return { imports: finalRule.imports ?? [], propertyName, column: ` ${finalRule.decorator}\n declare ${propertyName}: ${tsType}`, }; } /** * Generate schema for a single table (internal method) */ generateTableSchema(tableName, columns, importsBag) { const schema = Object.keys(columns).map((columnName) => { const column = columns[columnName]; return this.generateColumnSchema(columnName, column, tableName); }); // Add column-specific imports schema.forEach((col) => { col.imports.forEach((imp) => importsBag.add(imp)); }); const className = stringHelpers.pascalCase(stringHelpers.singular(tableName)); // Return complete class definition as a single string (compact format) return [ `export class ${className}Schema extends BaseModel {`, ` static $columns = [${schema.map((k) => `'${k.propertyName}'`).join(', ')}] as const`, ` $columns = ${className}Schema.$columns`, schema.map((k) => k.column).join('\n'), '}', ].join('\n'); } /** * Generate schemas for multiple tables */ generateSchemas(tables) { const importsBag = new ImportsBag(); const classesToCreate = []; // Add base import importsBag.add({ source: '@adonisjs/lucid/orm', namedImports: ['BaseModel', 'column'] }); tables.forEach((table) => { const classDefinition = this.generateTableSchema(table.name, table.columns, importsBag); classesToCreate.push(classDefinition); }); return { imports: importsBag.toArray(), classes: classesToCreate, }; } /** * Get the final output string from generated schemas */ getOutput(schemas) { const importsBag = new ImportsBag(); schemas.imports.forEach((imp) => importsBag.add(imp)); return `${importsBag.toString()}\n\n${schemas.classes.join('\n\n')}\n`; } }