136 lines
4.1 KiB
TypeScript
136 lines
4.1 KiB
TypeScript
import { getColumnsForTable, getForeignKeysReferencing } from '#services/database_inspector'
|
|
import {
|
|
mapDBTypeToFrontend,
|
|
mapDBTypeToTs,
|
|
mapDBTypeToValidator,
|
|
} from '#services/module_generator/db_types'
|
|
import { camelCase, capitalize } from '#services/module_generator/naming'
|
|
|
|
export interface EnhancedColumn {
|
|
name: string
|
|
camelCase: string
|
|
type: string
|
|
isPrimary: boolean
|
|
isPassword: boolean
|
|
isEmail: boolean
|
|
isDateTime: boolean
|
|
isSpecial: boolean
|
|
isRequired: boolean
|
|
isNullable: boolean
|
|
validatorType: string
|
|
needsTrim: boolean
|
|
}
|
|
|
|
export interface TestField {
|
|
camelCase: string
|
|
sampleValue: string | number | boolean
|
|
column_name: string
|
|
}
|
|
|
|
export interface ModuleContext {
|
|
hasPassword: boolean
|
|
enhancedColumns: EnhancedColumn[]
|
|
createFields: TestField[]
|
|
updateFields: TestField[]
|
|
insertJson: string
|
|
frontendFields: Array<{ name: string; type: string }>
|
|
relationships: { imports: string[]; code: string[] }
|
|
}
|
|
|
|
/** Derives everything the templates and builders need from the table schema. */
|
|
export async function buildModuleContext(table: string): Promise<ModuleContext> {
|
|
const columns = await getColumnsForTable(table)
|
|
|
|
const enhancedColumns = columns.map((col): EnhancedColumn => {
|
|
const colName = col.column_name
|
|
const type = mapDBTypeToTs(col.data_type) + (col.is_nullable === 'YES' ? ' | null' : '')
|
|
return {
|
|
name: colName,
|
|
camelCase: camelCase(colName),
|
|
type,
|
|
isPrimary: colName === 'id',
|
|
isPassword: colName === 'password',
|
|
isEmail: colName === 'email',
|
|
isDateTime: ['created_at', 'updated_at'].includes(colName),
|
|
isSpecial: ['created_at', 'updated_at', 'deleted_at'].includes(colName),
|
|
isRequired: col.is_nullable === 'NO',
|
|
isNullable: col.is_nullable === 'YES',
|
|
validatorType: mapDBTypeToValidator(col.data_type),
|
|
needsTrim:
|
|
mapDBTypeToValidator(col.data_type) === 'string' &&
|
|
colName !== 'email' &&
|
|
colName !== 'password',
|
|
}
|
|
})
|
|
|
|
const sampleFor = (col: EnhancedColumn) => {
|
|
const t = col.validatorType
|
|
if (t === 'number') return 1
|
|
if (t === 'boolean') return false
|
|
if (t === 'date') return `'${new Date().toISOString()}'`
|
|
return `'sample_${col.camelCase}'`
|
|
}
|
|
|
|
const toTestField = (col: EnhancedColumn): TestField => ({
|
|
camelCase: col.camelCase,
|
|
sampleValue: sampleFor(col),
|
|
column_name: col.name,
|
|
})
|
|
|
|
const createFields = enhancedColumns.filter((c) => !c.isPrimary && !c.isSpecial).map(toTestField)
|
|
const updateFields = enhancedColumns.filter((c) => !c.isPrimary && !c.isDateTime).map(toTestField)
|
|
|
|
// minimal insert json for show/update/destroy fixtures
|
|
const insertJson = `{
|
|
${createFields.map((f) => ` ${f.column_name}: ${f.sampleValue}`).join(',\n')}
|
|
}`
|
|
|
|
const frontendFields = columns.map((col) => ({
|
|
name: col.column_name,
|
|
type: mapDBTypeToFrontend(col.data_type) + (col.is_nullable === 'YES' ? ' | null' : ''),
|
|
}))
|
|
|
|
return {
|
|
hasPassword: columns.some((col) => col.column_name === 'password'),
|
|
enhancedColumns,
|
|
createFields,
|
|
updateFields,
|
|
insertJson,
|
|
frontendFields,
|
|
relationships: await buildRelationships(table),
|
|
}
|
|
}
|
|
|
|
/** Lucid relationship decorators inferred from the `<table>_id` column convention. */
|
|
async function buildRelationships(table: string) {
|
|
const lines: string[] = []
|
|
const imports: Set<string> = new Set()
|
|
|
|
const columns = await getColumnsForTable(table)
|
|
const references = await getForeignKeysReferencing(table)
|
|
|
|
for (const col of columns) {
|
|
if (col.column_name.endsWith('_id')) {
|
|
const refTable = col.column_name.replace('_id', '')
|
|
const refModel = capitalize(refTable)
|
|
imports.add('belongsTo')
|
|
lines.push(
|
|
` @belongsTo(() => ${refModel})\n declare ${refTable}: BelongsTo<typeof ${refModel}>`
|
|
)
|
|
}
|
|
}
|
|
|
|
for (const ref of references) {
|
|
const relModel = capitalize(ref.table_name)
|
|
imports.add('hasMany')
|
|
lines.push(
|
|
` @hasMany(() => ${relModel})\n declare ${ref.table_name}: HasMany<typeof ${relModel}>`
|
|
)
|
|
}
|
|
|
|
return {
|
|
imports: Array.from(imports),
|
|
code: lines,
|
|
}
|
|
}
|