Exposing Types for TypeScript Generation
The @ExposeType decorator allows you to explicitly expose types for TypeScript interface generation without requiring them to be directly referenced in REST endpoints, WebSocket messages, or as indirect dependencies.
Use Cases
The @ExposeType decorator is useful when you have types that:
- Are used only on the frontend - Types that exist for frontend state management, validation, or business logic
- Are part of a shared schema - Common types that multiple parts of your application use but aren't directly in API contracts
- Need to be pre-generated - Types that you want available immediately even if they're not yet used in any endpoints
- Are utility types - Helper types, enums, or constants that the frontend needs to know about
Basic Usage
Simply decorate any Pydantic model with @ExposeType():
from pydantic import BaseModel
from jararaca import ExposeType
@ExposeType()
class UserPermission(BaseModel):
id: str
name: str
description: str
resource: str
action: str
This type will now be included in the generated TypeScript output when you run:
Example: Frontend-Only Types
from pydantic import BaseModel
from jararaca import ExposeType
@ExposeType()
class FilterState(BaseModel):
"""Frontend state for table filtering."""
search_query: str
sort_column: str
sort_direction: str
page: int
page_size: int
@ExposeType()
class UITheme(BaseModel):
"""Frontend theme configuration."""
primary_color: str
secondary_color: str
dark_mode: bool
Example: Error Codes and Constants
from enum import Enum
from pydantic import BaseModel
from jararaca import ExposeType
@ExposeType()
class ErrorCode(str, Enum):
"""Standard error codes."""
UNAUTHORIZED = "UNAUTHORIZED"
NOT_FOUND = "NOT_FOUND"
VALIDATION_ERROR = "VALIDATION_ERROR"
INTERNAL_ERROR = "INTERNAL_ERROR"
@ExposeType()
class ApiErrorDetail(BaseModel):
"""Detailed error information."""
code: ErrorCode
message: str
field: str | None = None
details: dict[str, str] | None = None
Example: Complex Nested Types
from pydantic import BaseModel
from jararaca import ExposeType
@ExposeType()
class Address(BaseModel):
street: str
city: str
country: str
postal_code: str
@ExposeType()
class ContactInfo(BaseModel):
email: str
phone: str | None = None
address: Address
@ExposeType()
class Organization(BaseModel):
"""Complete organization structure."""
id: str
name: str
contacts: list[ContactInfo]
settings: dict[str, str]
When you expose a type with nested structures, the decorator ensures that all related types are also included in the TypeScript generation.
Comparison: With vs Without @ExposeType
Without @ExposeType
class UserRole(BaseModel):
"""Only generated if used in an endpoint or as a dependency."""
id: str
name: str
@RestController("/api/users")
class UserController:
@Get("/{user_id}")
async def get_user(self, user_id: str) -> UserResponse:
# UserRole is only generated if UserResponse references it
return UserResponse(...)
With @ExposeType
@ExposeType()
class UserRole(BaseModel):
"""Always generated, available immediately."""
id: str
name: str
@RestController("/api/users")
class UserController:
@Get("/{user_id}")
async def get_user(self, user_id: str) -> UserResponse:
# UserRole is available in TypeScript even if not used yet
return UserResponse(...)
Integration with Other Decorators
The @ExposeType decorator works seamlessly with other TypeScript generation decorators:
from jararaca import ExposeType, SplitInputOutput
@ExposeType()
@SplitInputOutput()
class UserProfile(BaseModel):
"""Generates UserProfileInput and UserProfileOutput interfaces."""
id: str
username: str
email: str
created_at: str
updated_at: str
This creates both UserProfileInput and UserProfileOutput TypeScript interfaces.
Best Practices
- Use for shared types: Apply
@ExposeTypeto types that are used across multiple parts of your application - Document the purpose: Add clear docstrings explaining why a type is exposed
- Avoid overuse: Only expose types that the frontend actually needs - don't expose internal implementation details
- Combine with other decorators: Use alongside
@SplitInputOutputwhen appropriate - Group related types: Keep exposed types in dedicated modules (e.g.,
shared_types.py)
Viewing Exposed Types
All types decorated with @ExposeType are tracked globally. You can check which types are exposed:
from jararaca.tools.typescript.decorators import ExposeType
# Get all exposed types
exposed = ExposeType.get_all_exposed_types()
print(f"Exposed {len(exposed)} types: {[t.__name__ for t in exposed]}")
Generated TypeScript
Given this Python code:
@ExposeType()
class NotificationPreference(BaseModel):
email_enabled: bool
push_enabled: bool
frequency: str
The generated TypeScript will be:
export interface NotificationPreference {
emailEnabled: boolean;
pushEnabled: boolean;
frequency: string;
}
The type is available in your TypeScript code even if no REST endpoint uses it yet.