222 lines
6.4 KiB
Go
222 lines
6.4 KiB
Go
package drizzle
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
)
|
|
|
|
// TemplateData represents the data passed to the template for code generation
|
|
type TemplateData struct {
|
|
Imports []string
|
|
Enums []*EnumData
|
|
Tables []*TableData
|
|
}
|
|
|
|
// EnumData represents an enum in the schema
|
|
type EnumData struct {
|
|
Name string // Enum name (PascalCase)
|
|
VarName string // Variable name for the enum (camelCase)
|
|
Values []string // Enum values
|
|
ValuesStr string // Comma-separated quoted values for pgEnum()
|
|
TypeUnion string // TypeScript union type (e.g., "'admin' | 'user' | 'guest'")
|
|
SchemaName string // Schema name
|
|
}
|
|
|
|
// TableData represents a table in the template
|
|
type TableData struct {
|
|
Name string // Table variable name (camelCase, e.g., users)
|
|
TableName string // Actual database table name (e.g., users)
|
|
TypeName string // TypeScript type name (PascalCase, e.g., Users)
|
|
Columns []*ColumnData // Column definitions
|
|
Indexes []*IndexData // Index definitions
|
|
Comment string // Table comment
|
|
SchemaName string // Schema name
|
|
NeedsSQLTag bool // Whether we need to import 'sql' from drizzle-orm
|
|
IndexColumnFields []string // Column field names used in indexes (for destructuring)
|
|
}
|
|
|
|
// ColumnData represents a column in a table
|
|
type ColumnData struct {
|
|
Name string // Column name in database
|
|
FieldName string // Field name in TypeScript (camelCase)
|
|
DrizzleChain string // Complete Drizzle column chain (e.g., "integer('id').primaryKey()")
|
|
TypeScriptType string // TypeScript type for interface (e.g., "string", "number | null")
|
|
IsForeignKey bool // Whether this is a foreign key
|
|
ReferencesLine string // The .references() line if FK
|
|
Comment string // Column comment
|
|
}
|
|
|
|
// IndexData represents an index definition
|
|
type IndexData struct {
|
|
Name string // Index name
|
|
Columns []string // Column names
|
|
IsUnique bool // Whether it's a unique index
|
|
Definition string // Complete index definition line
|
|
}
|
|
|
|
// NewTemplateData creates a new TemplateData
|
|
func NewTemplateData() *TemplateData {
|
|
return &TemplateData{
|
|
Imports: make([]string, 0),
|
|
Enums: make([]*EnumData, 0),
|
|
Tables: make([]*TableData, 0),
|
|
}
|
|
}
|
|
|
|
// AddImport adds an import to the template data (deduplicates automatically)
|
|
func (td *TemplateData) AddImport(importLine string) {
|
|
// Check if already exists
|
|
for _, imp := range td.Imports {
|
|
if imp == importLine {
|
|
return
|
|
}
|
|
}
|
|
td.Imports = append(td.Imports, importLine)
|
|
}
|
|
|
|
// AddEnum adds an enum to the template data
|
|
func (td *TemplateData) AddEnum(enum *EnumData) {
|
|
td.Enums = append(td.Enums, enum)
|
|
}
|
|
|
|
// AddTable adds a table to the template data
|
|
func (td *TemplateData) AddTable(table *TableData) {
|
|
td.Tables = append(td.Tables, table)
|
|
}
|
|
|
|
// FinalizeImports sorts imports
|
|
func (td *TemplateData) FinalizeImports() {
|
|
sort.Strings(td.Imports)
|
|
}
|
|
|
|
// NewEnumData creates EnumData from a models.Enum
|
|
func NewEnumData(enum *models.Enum, tm *TypeMapper) *EnumData {
|
|
// Keep enum name as-is (it should already be PascalCase from the source)
|
|
enumName := enum.Name
|
|
// Variable name is camelCase version
|
|
varName := tm.ToCamelCase(enum.Name)
|
|
|
|
// Format values as comma-separated quoted strings for pgEnum()
|
|
quotedValues := make([]string, len(enum.Values))
|
|
for i, v := range enum.Values {
|
|
quotedValues[i] = "'" + v + "'"
|
|
}
|
|
valuesStr := ""
|
|
for i, qv := range quotedValues {
|
|
if i > 0 {
|
|
valuesStr += ", "
|
|
}
|
|
valuesStr += qv
|
|
}
|
|
|
|
// Build TypeScript union type (e.g., "'admin' | 'user' | 'guest'")
|
|
typeUnion := ""
|
|
for i, qv := range quotedValues {
|
|
if i > 0 {
|
|
typeUnion += " | "
|
|
}
|
|
typeUnion += qv
|
|
}
|
|
|
|
return &EnumData{
|
|
Name: enumName,
|
|
VarName: varName,
|
|
Values: enum.Values,
|
|
ValuesStr: valuesStr,
|
|
TypeUnion: typeUnion,
|
|
SchemaName: enum.Schema,
|
|
}
|
|
}
|
|
|
|
// NewTableData creates TableData from a models.Table
|
|
func NewTableData(table *models.Table, tm *TypeMapper) *TableData {
|
|
tableName := tm.ToCamelCase(table.Name)
|
|
typeName := tm.ToPascalCase(table.Name)
|
|
|
|
return &TableData{
|
|
Name: tableName,
|
|
TableName: table.Name,
|
|
TypeName: typeName,
|
|
Columns: make([]*ColumnData, 0),
|
|
Indexes: make([]*IndexData, 0),
|
|
Comment: formatComment(table.Description, table.Comment),
|
|
SchemaName: table.Schema,
|
|
}
|
|
}
|
|
|
|
// AddColumn adds a column to the table data
|
|
func (td *TableData) AddColumn(col *ColumnData) {
|
|
td.Columns = append(td.Columns, col)
|
|
}
|
|
|
|
// AddIndex adds an index to the table data
|
|
func (td *TableData) AddIndex(idx *IndexData) {
|
|
td.Indexes = append(td.Indexes, idx)
|
|
}
|
|
|
|
// NewColumnData creates ColumnData from a models.Column
|
|
func NewColumnData(col *models.Column, table *models.Table, tm *TypeMapper, isEnum bool) *ColumnData {
|
|
fieldName := tm.ToCamelCase(col.Name)
|
|
drizzleChain := tm.BuildColumnChain(col, table, isEnum)
|
|
|
|
return &ColumnData{
|
|
Name: col.Name,
|
|
FieldName: fieldName,
|
|
DrizzleChain: drizzleChain,
|
|
Comment: formatComment(col.Description, col.Comment),
|
|
}
|
|
}
|
|
|
|
// NewIndexData creates IndexData from a models.Index
|
|
func NewIndexData(index *models.Index, tableVar string, tm *TypeMapper) *IndexData {
|
|
indexName := tm.ToCamelCase(index.Name) + "Idx"
|
|
|
|
// Build column references as field names (will be used with destructuring)
|
|
colRefs := make([]string, len(index.Columns))
|
|
for i, colName := range index.Columns {
|
|
// Use just the field name for destructured parameters
|
|
colRefs[i] = tm.ToCamelCase(colName)
|
|
}
|
|
|
|
// Build the complete definition
|
|
// Example: index('email_idx').on(email)
|
|
// or: uniqueIndex('unique_email_idx').on(email)
|
|
definition := ""
|
|
if index.Unique {
|
|
definition = "uniqueIndex('" + index.Name + "').on(" + joinStrings(colRefs, ", ") + ")"
|
|
} else {
|
|
definition = "index('" + index.Name + "').on(" + joinStrings(colRefs, ", ") + ")"
|
|
}
|
|
|
|
return &IndexData{
|
|
Name: indexName,
|
|
Columns: index.Columns,
|
|
IsUnique: index.Unique,
|
|
Definition: definition,
|
|
}
|
|
}
|
|
|
|
// formatComment combines description and comment into a single comment string
|
|
func formatComment(description, comment string) string {
|
|
if description != "" && comment != "" {
|
|
return description + " - " + comment
|
|
}
|
|
if description != "" {
|
|
return description
|
|
}
|
|
return comment
|
|
}
|
|
|
|
// joinStrings joins a slice of strings with a separator
|
|
func joinStrings(strs []string, sep string) string {
|
|
result := ""
|
|
for i, s := range strs {
|
|
if i > 0 {
|
|
result += sep
|
|
}
|
|
result += s
|
|
}
|
|
return result
|
|
}
|