Files
relspecgo/pkg/writers/drizzle/template_data.go
Hein 35bc9dfb5c
Some checks failed
CI / Test (1.24) (push) Failing after -24m8s
CI / Test (1.25) (push) Failing after -23m54s
CI / Lint (push) Failing after -25m2s
CI / Build (push) Successful in -25m18s
Added Drizzle ORM support
2025-12-28 10:15:30 +02:00

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
}