Type System
This guide explains how to maintain type safety between Go backend services and TypeScript frontend applications. Go types are the single source of truth, and TypeScript types are auto-generated.
The Golden Rule
Never create TypeScript types manually for backend data structures.
All backend types come from Go via tygo generation.
Type Generation Workflow
Go Types (Source) → tygo generate → TypeScript Types
agent-models.go via make generate agent-models.ts
Running Type Generation
cd /Users/ahmed/workspace/eloquent/eloquent
make generate
Run this command:
- After modifying any Go types
- After pulling changes that include Go type updates
- Before starting frontend work if unsure about type freshness
Go Type Definition
Basic Types
// In common/entities/entity.go
type EntityDefinition struct {
ID uuid.UUID `json:"id,omitempty"`
Name string `json:"name"`
DisplayName string `json:"displayName"`
Description string `json:"description,omitempty"`
Schema types.JSONSchema `json:"schema" tstype:"JSONSchema"`
IsActive bool `json:"isActive"`
Version int `json:"version"`
CreatedAt time.Time `json:"createdAt"`
}
Generated TypeScript
// In packages/eloquent/entity/models/entity.ts
// Code generated by tygo. DO NOT EDIT.
export interface EntityDefinition {
id?: string /* uuid */;
name: string;
displayName: string;
description?: string;
schema: JSONSchema;
isActive: boolean;
version: number;
createdAt: string /* RFC3339 */;
}
Go Annotations
json Tags
Control field names and optionality:
// Required field
Name string `json:"name"`
// Optional field (generates ?: in TypeScript)
Description string `json:"description,omitempty"`
// Skip field entirely
InternalField string `json:"-"`
tstype Tag
Override the generated TypeScript type:
// Use custom TypeScript type instead of generated one
Schema types.JSONSchema `json:"schema" tstype:"JSONSchema"`
// Use union type
Status AgentStatus `json:"status" tstype:"AgentStatusTS"`
tygo:emit Directive
Emit custom TypeScript code (useful for union types):
// Define the Go type
type AgentStatus string
const (
AgentStatusDraft AgentStatus = "draft"
AgentStatusActive AgentStatus = "active"
AgentStatusInactive AgentStatus = "inactive"
)
// Emit TypeScript union type
//tygo:emit
var _ = `export type AgentStatusTS = 'draft' | 'active' | 'inactive';`
Configuration
tygo.yaml Structure
packages:
- path: "blazi/common/entities"
output_path: "../../eloquent/eloquent-packages/packages/entity/models/entity.ts"
frontmatter: |
import { JSONSchema } from "@elqnt/types";
type_mappings:
time.Time: "string /* RFC3339 */"
uuid.UUID: "string /* uuid */"
types.JSONSchema: "JSONSchema"
Common Type Mappings
| Go Type | TypeScript Type | Notes |
|---|---|---|
time.Time | string /* RFC3339 */ | ISO 8601 date string |
uuid.UUID | string /* uuid */ | UUID string format |
uuid.NullUUID | null | string /* uuid */ | Nullable UUID |
null.String | null | string | SQL nullable string |
types.JSONSchema | JSONSchema | JSON Schema type |
map[string]any | Record<string, any> | Generic object |
Package Mappings
| Go Package | TypeScript Package |
|---|---|
blazi/common/agents | @elqnt/agents |
blazi/common/entities | @elqnt/entity |
blazi/common/workflows | @elqnt/workflow |
blazi/common/kg | @elqnt/kg |
blazi/common/types | @elqnt/types |
Frontend Usage
Importing Generated Types
import type { EntityDefinition, EntityRecord } from "@elqnt/entity/models";
import type { Agent, AgentTool } from "@elqnt/agents/models";
import type { KGNode } from "@elqnt/kg/models";
Using Types with useEntities Hook
import { useEntities } from "@elqnt/entity/hooks";
import type { EntityRecord } from "@elqnt/entity/models";
// App-specific model (camelCase)
interface Ticket {
id: string;
title: string;
status: string;
}
// Transform EntityRecord → App model
function toTicket(record: EntityRecord): Ticket {
return {
id: record.id,
title: record.fields.title,
status: record.fields.status,
};
}
function useTickets() {
const { createRecord, loading, error } = useEntities({
baseUrl: config.apiGatewayUrl,
orgId: config.orgId,
});
const createTicket = async (data: Omit<Ticket, "id">) => {
const record = await createRecord("ticket", { fields: data });
return record ? toTicket(record) : null;
};
return { createTicket, loading, error };
}
What Types to Create Manually
Only create TypeScript types for frontend-specific needs:
// Client-specific UI state
interface TicketFormState {
isSubmitting: boolean;
validationErrors: Record<string, string>;
selectedTab: "details" | "comments";
}
// Component props
interface TicketCardProps {
ticket: Ticket;
onEdit: (id: string) => void;
isSelected: boolean;
}
// App-specific model (transformed from EntityRecord)
interface Ticket {
id: string;
title: string;
status: string;
}
Troubleshooting
Types Not Updating
- Ensure Go file is saved
- Run
make generatefrom backend root - Check tygo.yaml has correct path mapping
- Verify the output file path exists
Import Errors in Generated File
Add missing imports to frontmatter in tygo.yaml:
frontmatter: |
import { JSONSchema } from "@elqnt/types";
import { ResponseMetadata } from "@elqnt/types";
Best Practices
- Always Define in Go First - Go is the source of truth
- Add Comments for Documentation - Comments are preserved
- Use Meaningful Type Names - Be specific (TicketStatus vs Status)
- Regenerate After Any Change - Run
make generate - Never Edit Generated Files - Changes will be lost
Next Steps
- Backend Services - Creating Go services
- Frontend Apps - Using types in React
- Entities Management - Working with entities