Eloquent

Documentation

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 TypeTypeScript TypeNotes
time.Timestring /* RFC3339 */ISO 8601 date string
uuid.UUIDstring /* uuid */UUID string format
uuid.NullUUIDnull | string /* uuid */Nullable UUID
null.Stringnull | stringSQL nullable string
types.JSONSchemaJSONSchemaJSON Schema type
map[string]anyRecord<string, any>Generic object

Package Mappings

Go PackageTypeScript 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

  1. Ensure Go file is saved
  2. Run make generate from backend root
  3. Check tygo.yaml has correct path mapping
  4. 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

  1. Always Define in Go First - Go is the source of truth
  2. Add Comments for Documentation - Comments are preserved
  3. Use Meaningful Type Names - Be specific (TicketStatus vs Status)
  4. Regenerate After Any Change - Run make generate
  5. Never Edit Generated Files - Changes will be lost

Next Steps