jararaca 0.3.14__tar.gz → 0.3.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of jararaca might be problematic. Click here for more details.

Files changed (91) hide show
  1. {jararaca-0.3.14 → jararaca-0.3.16}/PKG-INFO +2 -1
  2. {jararaca-0.3.14 → jararaca-0.3.16}/README.md +1 -0
  3. jararaca-0.3.16/docs/expose-type.md +221 -0
  4. {jararaca-0.3.14 → jararaca-0.3.16}/pyproject.toml +41 -1
  5. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/__init__.py +3 -0
  6. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/decorators.py +6 -3
  7. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/tools/typescript/decorators.py +46 -0
  8. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/tools/typescript/interface_parser.py +8 -2
  9. {jararaca-0.3.14 → jararaca-0.3.16}/LICENSE +0 -0
  10. {jararaca-0.3.14 → jararaca-0.3.16}/docs/CNAME +0 -0
  11. {jararaca-0.3.14 → jararaca-0.3.16}/docs/architecture.md +0 -0
  12. {jararaca-0.3.14 → jararaca-0.3.16}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg +0 -0
  13. {jararaca-0.3.14 → jararaca-0.3.16}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp +0 -0
  14. {jararaca-0.3.14 → jararaca-0.3.16}/docs/assets/tracing_example.png +0 -0
  15. {jararaca-0.3.14 → jararaca-0.3.16}/docs/http-rpc.md +0 -0
  16. {jararaca-0.3.14 → jararaca-0.3.16}/docs/index.md +0 -0
  17. {jararaca-0.3.14 → jararaca-0.3.16}/docs/interceptors.md +0 -0
  18. {jararaca-0.3.14 → jararaca-0.3.16}/docs/messagebus.md +0 -0
  19. {jararaca-0.3.14 → jararaca-0.3.16}/docs/retry.md +0 -0
  20. {jararaca-0.3.14 → jararaca-0.3.16}/docs/scheduler.md +0 -0
  21. {jararaca-0.3.14 → jararaca-0.3.16}/docs/stylesheets/custom.css +0 -0
  22. {jararaca-0.3.14 → jararaca-0.3.16}/docs/websocket.md +0 -0
  23. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/__main__.py +0 -0
  24. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/broker_backend/__init__.py +0 -0
  25. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/broker_backend/mapper.py +0 -0
  26. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/broker_backend/redis_broker_backend.py +0 -0
  27. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/cli.py +0 -0
  28. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/common/__init__.py +0 -0
  29. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/core/__init__.py +0 -0
  30. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/core/providers.py +0 -0
  31. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/core/uow.py +0 -0
  32. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/di.py +0 -0
  33. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/files/entity.py.mako +0 -0
  34. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/lifecycle.py +0 -0
  35. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/__init__.py +0 -0
  36. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/bus_message_controller.py +0 -0
  37. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/consumers/__init__.py +0 -0
  38. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/interceptors/__init__.py +0 -0
  39. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +0 -0
  40. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/interceptors/publisher_interceptor.py +0 -0
  41. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/message.py +0 -0
  42. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/publisher.py +0 -0
  43. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/messagebus/worker.py +0 -0
  44. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/microservice.py +0 -0
  45. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/observability/decorators.py +0 -0
  46. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/observability/interceptor.py +0 -0
  47. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/observability/providers/__init__.py +0 -0
  48. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/observability/providers/otel.py +0 -0
  49. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/base.py +0 -0
  50. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/exports.py +0 -0
  51. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/interceptors/__init__.py +0 -0
  52. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py +0 -0
  53. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/interceptors/constants.py +0 -0
  54. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/interceptors/decorators.py +0 -0
  55. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/session.py +0 -0
  56. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/sort_filter.py +0 -0
  57. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/persistence/utilities.py +0 -0
  58. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/__init__.py +0 -0
  59. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/decorators.py +0 -0
  60. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/hooks.py +0 -0
  61. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/http_microservice.py +0 -0
  62. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/server.py +0 -0
  63. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/websocket/__init__.py +0 -0
  64. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/websocket/base_types.py +0 -0
  65. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/websocket/context.py +0 -0
  66. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/websocket/decorators.py +0 -0
  67. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/websocket/redis.py +0 -0
  68. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/websocket/types.py +0 -0
  69. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/presentation/websocket/websocket_interceptor.py +0 -0
  70. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/py.typed +0 -0
  71. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/reflect/__init__.py +0 -0
  72. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/reflect/controller_inspect.py +0 -0
  73. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/reflect/metadata.py +0 -0
  74. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/rpc/__init__.py +0 -0
  75. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/rpc/http/__init__.py +0 -0
  76. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/rpc/http/backends/__init__.py +0 -0
  77. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/rpc/http/backends/httpx.py +0 -0
  78. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/rpc/http/backends/otel.py +0 -0
  79. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/rpc/http/decorators.py +0 -0
  80. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/rpc/http/httpx.py +0 -0
  81. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/scheduler/__init__.py +0 -0
  82. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/scheduler/beat_worker.py +0 -0
  83. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/scheduler/decorators.py +0 -0
  84. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/scheduler/types.py +0 -0
  85. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/tools/app_config/__init__.py +0 -0
  86. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/tools/app_config/decorators.py +0 -0
  87. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/tools/app_config/interceptor.py +0 -0
  88. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/tools/typescript/__init__.py +0 -0
  89. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/utils/__init__.py +0 -0
  90. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/utils/rabbitmq_utils.py +0 -0
  91. {jararaca-0.3.14 → jararaca-0.3.16}/src/jararaca/utils/retry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jararaca
3
- Version: 0.3.14
3
+ Version: 0.3.16
4
4
  Summary: A simple and fast API framework for Python
5
5
  Home-page: https://github.com/LuscasLeo/jararaca
6
6
  Author: Lucas S
@@ -74,6 +74,7 @@ Jararaca is an async-first microservice framework designed to simplify the devel
74
74
  - Command-line tool for generating TypeScript types
75
75
  - Support for REST endpoints, WebSocket events, and message bus payloads
76
76
  - Type-safe frontend-backend communication
77
+ - **`@ExposeType` decorator** - Explicitly expose types for TypeScript generation without needing them in endpoints
77
78
 
78
79
  ### Hexagonal Architecture
79
80
  - Clear separation of concerns
@@ -37,6 +37,7 @@ Jararaca is an async-first microservice framework designed to simplify the devel
37
37
  - Command-line tool for generating TypeScript types
38
38
  - Support for REST endpoints, WebSocket events, and message bus payloads
39
39
  - Type-safe frontend-backend communication
40
+ - **`@ExposeType` decorator** - Explicitly expose types for TypeScript generation without needing them in endpoints
40
41
 
41
42
  ### Hexagonal Architecture
42
43
  - Clear separation of concerns
@@ -0,0 +1,221 @@
1
+ # Exposing Types for TypeScript Generation
2
+
3
+ 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.
4
+
5
+ ## Use Cases
6
+
7
+ The `@ExposeType` decorator is useful when you have types that:
8
+
9
+ 1. **Are used only on the frontend** - Types that exist for frontend state management, validation, or business logic
10
+ 2. **Are part of a shared schema** - Common types that multiple parts of your application use but aren't directly in API contracts
11
+ 3. **Need to be pre-generated** - Types that you want available immediately even if they're not yet used in any endpoints
12
+ 4. **Are utility types** - Helper types, enums, or constants that the frontend needs to know about
13
+
14
+ ## Basic Usage
15
+
16
+ Simply decorate any Pydantic model with `@ExposeType()`:
17
+
18
+ ```python
19
+ from pydantic import BaseModel
20
+
21
+ from jararaca import ExposeType
22
+
23
+
24
+ @ExposeType()
25
+ class UserPermission(BaseModel):
26
+ id: str
27
+ name: str
28
+ description: str
29
+ resource: str
30
+ action: str
31
+ ```
32
+
33
+ This type will now be included in the generated TypeScript output when you run:
34
+
35
+ ```bash
36
+ jararaca gen-tsi app:app output.ts
37
+ ```
38
+
39
+ ## Example: Frontend-Only Types
40
+
41
+ ```python
42
+ from pydantic import BaseModel
43
+
44
+ from jararaca import ExposeType
45
+
46
+
47
+ @ExposeType()
48
+ class FilterState(BaseModel):
49
+ """Frontend state for table filtering."""
50
+ search_query: str
51
+ sort_column: str
52
+ sort_direction: str
53
+ page: int
54
+ page_size: int
55
+
56
+ @ExposeType()
57
+ class UITheme(BaseModel):
58
+ """Frontend theme configuration."""
59
+ primary_color: str
60
+ secondary_color: str
61
+ dark_mode: bool
62
+ ```
63
+
64
+ ## Example: Error Codes and Constants
65
+
66
+ ```python
67
+ from enum import Enum
68
+
69
+ from pydantic import BaseModel
70
+
71
+ from jararaca import ExposeType
72
+
73
+
74
+ @ExposeType()
75
+ class ErrorCode(str, Enum):
76
+ """Standard error codes."""
77
+ UNAUTHORIZED = "UNAUTHORIZED"
78
+ NOT_FOUND = "NOT_FOUND"
79
+ VALIDATION_ERROR = "VALIDATION_ERROR"
80
+ INTERNAL_ERROR = "INTERNAL_ERROR"
81
+
82
+ @ExposeType()
83
+ class ApiErrorDetail(BaseModel):
84
+ """Detailed error information."""
85
+ code: ErrorCode
86
+ message: str
87
+ field: str | None = None
88
+ details: dict[str, str] | None = None
89
+ ```
90
+
91
+ ## Example: Complex Nested Types
92
+
93
+ ```python
94
+ from pydantic import BaseModel
95
+
96
+ from jararaca import ExposeType
97
+
98
+
99
+ @ExposeType()
100
+ class Address(BaseModel):
101
+ street: str
102
+ city: str
103
+ country: str
104
+ postal_code: str
105
+
106
+ @ExposeType()
107
+ class ContactInfo(BaseModel):
108
+ email: str
109
+ phone: str | None = None
110
+ address: Address
111
+
112
+ @ExposeType()
113
+ class Organization(BaseModel):
114
+ """Complete organization structure."""
115
+ id: str
116
+ name: str
117
+ contacts: list[ContactInfo]
118
+ settings: dict[str, str]
119
+ ```
120
+
121
+ When you expose a type with nested structures, the decorator ensures that all related types are also included in the TypeScript generation.
122
+
123
+ ## Comparison: With vs Without @ExposeType
124
+
125
+ ### Without @ExposeType
126
+
127
+ ```python
128
+ class UserRole(BaseModel):
129
+ """Only generated if used in an endpoint or as a dependency."""
130
+ id: str
131
+ name: str
132
+
133
+ @RestController("/api/users")
134
+ class UserController:
135
+ @Get("/{user_id}")
136
+ async def get_user(self, user_id: str) -> UserResponse:
137
+ # UserRole is only generated if UserResponse references it
138
+ return UserResponse(...)
139
+ ```
140
+
141
+ ### With @ExposeType
142
+
143
+ ```python
144
+ @ExposeType()
145
+ class UserRole(BaseModel):
146
+ """Always generated, available immediately."""
147
+ id: str
148
+ name: str
149
+
150
+ @RestController("/api/users")
151
+ class UserController:
152
+ @Get("/{user_id}")
153
+ async def get_user(self, user_id: str) -> UserResponse:
154
+ # UserRole is available in TypeScript even if not used yet
155
+ return UserResponse(...)
156
+ ```
157
+
158
+ ## Integration with Other Decorators
159
+
160
+ The `@ExposeType` decorator works seamlessly with other TypeScript generation decorators:
161
+
162
+ ```python
163
+ from jararaca import ExposeType, SplitInputOutput
164
+
165
+
166
+ @ExposeType()
167
+ @SplitInputOutput()
168
+ class UserProfile(BaseModel):
169
+ """Generates UserProfileInput and UserProfileOutput interfaces."""
170
+ id: str
171
+ username: str
172
+ email: str
173
+ created_at: str
174
+ updated_at: str
175
+ ```
176
+
177
+ This creates both `UserProfileInput` and `UserProfileOutput` TypeScript interfaces.
178
+
179
+ ## Best Practices
180
+
181
+ 1. **Use for shared types**: Apply `@ExposeType` to types that are used across multiple parts of your application
182
+ 2. **Document the purpose**: Add clear docstrings explaining why a type is exposed
183
+ 3. **Avoid overuse**: Only expose types that the frontend actually needs - don't expose internal implementation details
184
+ 4. **Combine with other decorators**: Use alongside `@SplitInputOutput` when appropriate
185
+ 5. **Group related types**: Keep exposed types in dedicated modules (e.g., `shared_types.py`)
186
+
187
+ ## Viewing Exposed Types
188
+
189
+ All types decorated with `@ExposeType` are tracked globally. You can check which types are exposed:
190
+
191
+ ```python
192
+ from jararaca.tools.typescript.decorators import ExposeType
193
+
194
+ # Get all exposed types
195
+ exposed = ExposeType.get_all_exposed_types()
196
+ print(f"Exposed {len(exposed)} types: {[t.__name__ for t in exposed]}")
197
+ ```
198
+
199
+ ## Generated TypeScript
200
+
201
+ Given this Python code:
202
+
203
+ ```python
204
+ @ExposeType()
205
+ class NotificationPreference(BaseModel):
206
+ email_enabled: bool
207
+ push_enabled: bool
208
+ frequency: str
209
+ ```
210
+
211
+ The generated TypeScript will be:
212
+
213
+ ```typescript
214
+ export interface NotificationPreference {
215
+ emailEnabled: boolean;
216
+ pushEnabled: boolean;
217
+ frequency: string;
218
+ }
219
+ ```
220
+
221
+ The type is available in your TypeScript code even if no REST endpoint uses it yet.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "jararaca"
3
- version = "0.3.14"
3
+ version = "0.3.16"
4
4
  description = "A simple and fast API framework for Python"
5
5
  authors = ["Lucas S <me@luscasleo.dev>"]
6
6
  readme = "README.md"
@@ -62,6 +62,10 @@ mkdocs-mermaid2-plugin = "^1.2.1"
62
62
  [tool.poetry.group.dev.dependencies]
63
63
  httptools = "^0.6.1"
64
64
  httpx = "^0.27.2"
65
+ pytest = "^8.0.0"
66
+ pytest-asyncio = "^0.23.0"
67
+ pytest-cov = "^4.1.0"
68
+ pytest-mock = "^3.12.0"
65
69
 
66
70
  [build-system]
67
71
  requires = ["poetry-core"]
@@ -84,3 +88,39 @@ jararaca = "jararaca.cli:cli"
84
88
  [[tool.mypy.overrides]]
85
89
  module = "mako.*"
86
90
  ignore_missing_imports = true
91
+
92
+ [tool.pytest.ini_options]
93
+ testpaths = ["tests"]
94
+ python_files = ["test_*.py"]
95
+ python_classes = ["Test*"]
96
+ python_functions = ["test_*"]
97
+ asyncio_mode = "auto"
98
+ addopts = [
99
+ "--strict-markers",
100
+ "--strict-config",
101
+ "--showlocals",
102
+ ]
103
+ markers = [
104
+ "unit: Unit tests",
105
+ "integration: Integration tests",
106
+ "slow: Slow tests",
107
+ ]
108
+
109
+ [tool.coverage.run]
110
+ source = ["src/jararaca"]
111
+ omit = [
112
+ "*/tests/*",
113
+ "*/__pycache__/*",
114
+ "*/.venv/*",
115
+ ]
116
+
117
+ [tool.coverage.report]
118
+ exclude_lines = [
119
+ "pragma: no cover",
120
+ "def __repr__",
121
+ "raise AssertionError",
122
+ "raise NotImplementedError",
123
+ "if __name__ == .__main__.:",
124
+ "if TYPE_CHECKING:",
125
+ "@abstractmethod",
126
+ ]
@@ -171,6 +171,7 @@ if TYPE_CHECKING:
171
171
  from .scheduler.decorators import ScheduledAction
172
172
  from .tools.app_config.interceptor import AppConfigurationInterceptor
173
173
  from .tools.typescript.decorators import (
174
+ ExposeType,
174
175
  MutationEndpoint,
175
176
  QueryEndpoint,
176
177
  SplitInputOutput,
@@ -279,6 +280,7 @@ if TYPE_CHECKING:
279
280
  "MessageBusPublisherInterceptor",
280
281
  "RedisWebSocketConnectionBackend",
281
282
  "AppConfigurationInterceptor",
283
+ "ExposeType",
282
284
  "QueryEndpoint",
283
285
  "MutationEndpoint",
284
286
  "SplitInputOutput",
@@ -510,6 +512,7 @@ _dynamic_imports: "dict[str, tuple[str, str, str | None]]" = {
510
512
  "tools.app_config.interceptor",
511
513
  None,
512
514
  ),
515
+ "ExposeType": (__SPEC_PARENT__, "tools.typescript.decorators", None),
513
516
  "QueryEndpoint": (__SPEC_PARENT__, "tools.typescript.decorators", None),
514
517
  "MutationEndpoint": (__SPEC_PARENT__, "tools.typescript.decorators", None),
515
518
  "SplitInputOutput": (__SPEC_PARENT__, "tools.typescript.decorators", None),
@@ -11,6 +11,8 @@ from jararaca.scheduler.decorators import ScheduledAction, ScheduledActionData
11
11
 
12
12
  DECORATED_FUNC = TypeVar("DECORATED_FUNC", bound=Callable[..., Any])
13
13
  DECORATED_T = TypeVar("DECORATED_T", bound=Any)
14
+ INSTANCE_T = TypeVar("INSTANCE_T", bound=Any)
15
+ RETURN_T = TypeVar("RETURN_T", bound=Any)
14
16
 
15
17
 
16
18
  class MessageHandler(Generic[INHERITS_MESSAGE_CO]):
@@ -35,8 +37,9 @@ class MessageHandler(Generic[INHERITS_MESSAGE_CO]):
35
37
  self.name = name
36
38
 
37
39
  def __call__(
38
- self, func: Callable[[Any, MessageOf[INHERITS_MESSAGE_CO]], Awaitable[None]]
39
- ) -> Callable[[Any, MessageOf[INHERITS_MESSAGE_CO]], Awaitable[None]]:
40
+ self,
41
+ func: Callable[[INSTANCE_T, MessageOf[INHERITS_MESSAGE_CO]], Awaitable[None]],
42
+ ) -> Callable[[INSTANCE_T, MessageOf[INHERITS_MESSAGE_CO]], Awaitable[None]]:
40
43
 
41
44
  MessageHandler[Any].register(func, self)
42
45
 
@@ -52,7 +55,7 @@ class MessageHandler(Generic[INHERITS_MESSAGE_CO]):
52
55
 
53
56
  @staticmethod
54
57
  def get_message_incoming(
55
- func: Callable[[MessageOf[Any]], Awaitable[Any]],
58
+ func: Callable[[Any, MessageOf[Any]], Awaitable[None]],
56
59
  ) -> "MessageHandler[Message] | None":
57
60
  if not hasattr(func, MessageHandler.MESSAGE_INCOMING_ATTR):
58
61
  return None
@@ -93,3 +93,49 @@ class SplitInputOutput:
93
93
  Check if the Pydantic model is marked for split interface generation.
94
94
  """
95
95
  return getattr(cls, SplitInputOutput.METADATA_KEY, False)
96
+
97
+
98
+ class ExposeType:
99
+ """
100
+ Decorator to explicitly expose types for TypeScript interface generation.
101
+
102
+ Use this decorator to include types in the generated TypeScript output without
103
+ needing them as request/response bodies or indirect dependencies.
104
+
105
+ Example:
106
+ @ExposeType()
107
+ class UserRole(BaseModel):
108
+ id: str
109
+ name: str
110
+
111
+ # This ensures UserRole interface is generated even if it's not
112
+ # directly referenced in any REST endpoint
113
+ """
114
+
115
+ METADATA_KEY = "__jararaca_expose_type__"
116
+ _exposed_types: set[type] = set()
117
+
118
+ def __init__(self) -> None:
119
+ pass
120
+
121
+ def __call__(self, cls: type[BASEMODEL_T]) -> type[BASEMODEL_T]:
122
+ """
123
+ Decorate the type to mark it for explicit TypeScript generation.
124
+ """
125
+ setattr(cls, self.METADATA_KEY, True)
126
+ ExposeType._exposed_types.add(cls)
127
+ return cls
128
+
129
+ @staticmethod
130
+ def is_exposed_type(cls: type) -> bool:
131
+ """
132
+ Check if the type is marked for explicit exposure.
133
+ """
134
+ return getattr(cls, ExposeType.METADATA_KEY, False)
135
+
136
+ @staticmethod
137
+ def get_all_exposed_types() -> set[type]:
138
+ """
139
+ Get all types that have been marked for explicit exposure.
140
+ """
141
+ return ExposeType._exposed_types.copy()
@@ -36,6 +36,7 @@ from jararaca.presentation.websocket.websocket_interceptor import (
36
36
  WebSocketMessageWrapper,
37
37
  )
38
38
  from jararaca.tools.typescript.decorators import (
39
+ ExposeType,
39
40
  MutationEndpoint,
40
41
  QueryEndpoint,
41
42
  SplitInputOutput,
@@ -661,6 +662,9 @@ def write_microservice_to_typescript_interface(
661
662
  websocket_registries: set[RegisterWebSocketMessage] = set()
662
663
  mapped_types_set.add(WebSocketMessageWrapper)
663
664
 
665
+ # Add all explicitly exposed types
666
+ mapped_types_set.update(ExposeType.get_all_exposed_types())
667
+
664
668
  for controller in microservice.controllers:
665
669
  rest_controller = RestController.get_controller(controller)
666
670
 
@@ -700,7 +704,7 @@ def write_microservice_to_typescript_interface(
700
704
 
701
705
  // noinspection JSUnusedGlobalSymbols
702
706
 
703
- import { HttpService, HttpBackend, HttpBackendRequest, ResponseType, createClassQueryHooks , createClassMutationHooks, createClassInfiniteQueryHooks, paginationModelByFirstArgPaginationFilter } from "@jararaca/core";
707
+ import { HttpService, HttpBackend, HttpBackendRequest, ResponseType, createClassQueryHooks , createClassMutationHooks, createClassInfiniteQueryHooks, paginationModelByFirstArgPaginationFilter, recursiveCamelToSnakeCase } from "@jararaca/core";
704
708
 
705
709
  function makeFormData(data: Record<string, any>): FormData {
706
710
  const formData = new FormData();
@@ -723,7 +727,9 @@ function* genFormDataValue(value: any): any {
723
727
  } else if (typeof value === "object" && value.constructor === Object) {
724
728
  // Stringify plain objects as JSON
725
729
  // formData.append(key, JSON.stringify(value));
726
- yield JSON.stringify(value);
730
+ yield JSON.stringify(
731
+ recursiveCamelToSnakeCase(value)
732
+ );
727
733
  } else {
728
734
  // For primitives (string, number, boolean), append as-is
729
735
  yield value;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes