django-cfg 1.4.11__py3-none-any.whl → 1.4.13__py3-none-any.whl
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.
- django_cfg/core/generation/integration_generators/api.py +2 -1
- django_cfg/models/django/openapi.py +4 -80
- django_cfg/modules/django_client/core/archive/manager.py +2 -2
- django_cfg/modules/django_client/core/config/config.py +20 -0
- django_cfg/modules/django_client/core/config/service.py +1 -1
- django_cfg/modules/django_client/core/generator/__init__.py +4 -4
- django_cfg/modules/django_client/core/generator/base.py +71 -0
- django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
- django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
- django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
- django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
- django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
- django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
- django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/api_wrapper.py.jinja +25 -2
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client.py.jinja +24 -6
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client_file.py.jinja +1 -0
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/operation_method.py.jinja +3 -1
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/sub_client.py.jinja +8 -1
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/main_init.py.jinja +2 -0
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enum_class.py.jinja +3 -1
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/schema_class.py.jinja +3 -1
- django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
- django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
- django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
- django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/app_client.ts.jinja +1 -1
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/client.ts.jinja +77 -1
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/main_client_file.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/sub_client.ts.jinja +3 -3
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/main_index.ts.jinja +73 -11
- django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/errors.ts.jinja +3 -1
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/logger.ts.jinja +9 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/storage.ts.jinja +54 -10
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -0
- django_cfg/modules/django_client/pytest.ini +30 -0
- django_cfg/modules/django_client/spectacular/__init__.py +3 -2
- django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
- django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
- django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
- django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
- django_cfg/modules/django_unfold/callbacks/main.py +6 -6
- django_cfg/pyproject.toml +1 -1
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/METADATA +1 -1
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/RECORD +99 -70
- django_cfg/modules/django_client/core/generator/python.py +0 -751
- django_cfg/modules/django_client/core/generator/typescript.py +0 -872
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/__init__.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/app_init.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/app_client.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/flat_client.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client_file.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/app_models.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enums.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/models.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/logger.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/schema.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/app_index.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/flat_client.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/operation.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client_file.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/index.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/app_models.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/enums.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/models.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/http.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/schema.ts.jinja +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/management/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/management/commands/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/documentation.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,428 @@
|
|
1
|
+
"""
|
2
|
+
Fetchers Generator - Generates typed fetcher functions from IR.
|
3
|
+
|
4
|
+
This generator creates universal TypeScript functions that:
|
5
|
+
- Use Zod schemas for runtime validation
|
6
|
+
- Work in any environment (Next.js, React Native, Node.js)
|
7
|
+
- Are type-safe with proper TypeScript types
|
8
|
+
- Can be used with any data-fetching library
|
9
|
+
"""
|
10
|
+
|
11
|
+
from __future__ import annotations
|
12
|
+
|
13
|
+
from jinja2 import Environment
|
14
|
+
from ..base import GeneratedFile, BaseGenerator
|
15
|
+
from ...ir import IRContext, IROperationObject
|
16
|
+
|
17
|
+
|
18
|
+
class FetchersGenerator:
|
19
|
+
"""
|
20
|
+
Generate typed fetcher functions from IR operations.
|
21
|
+
|
22
|
+
Features:
|
23
|
+
- Runtime validation with Zod
|
24
|
+
- Type-safe parameters and responses
|
25
|
+
- Works with any data-fetching library (SWR, React Query)
|
26
|
+
- Server Component compatible
|
27
|
+
"""
|
28
|
+
|
29
|
+
def __init__(self, jinja_env: Environment, context: IRContext, base: BaseGenerator):
|
30
|
+
self.jinja_env = jinja_env
|
31
|
+
self.context = context
|
32
|
+
self.base = base
|
33
|
+
|
34
|
+
def generate_fetcher_function(self, operation: IROperationObject) -> str:
|
35
|
+
"""
|
36
|
+
Generate a single fetcher function for an operation.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
operation: IROperationObject to convert to fetcher
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
TypeScript fetcher function code
|
43
|
+
|
44
|
+
Examples:
|
45
|
+
>>> generate_fetcher_function(users_list)
|
46
|
+
export async function getUsers(params?: GetUsersParams): Promise<PaginatedUser> {
|
47
|
+
const response = await api.users.list(params)
|
48
|
+
return PaginatedUserSchema.parse(response)
|
49
|
+
}
|
50
|
+
"""
|
51
|
+
# Get function name (e.g., "getUsers", "createUser")
|
52
|
+
func_name = self._operation_to_function_name(operation)
|
53
|
+
|
54
|
+
# Get parameters structure
|
55
|
+
param_info = self._get_param_structure(operation)
|
56
|
+
|
57
|
+
# Get response type and schema
|
58
|
+
response_type, response_schema = self._get_response_info(operation)
|
59
|
+
|
60
|
+
# Get API client call
|
61
|
+
api_call = self._get_api_call(operation)
|
62
|
+
|
63
|
+
# Build JSDoc comment
|
64
|
+
jsdoc = self._generate_jsdoc(operation, func_name)
|
65
|
+
|
66
|
+
# Build function
|
67
|
+
lines = []
|
68
|
+
|
69
|
+
# JSDoc
|
70
|
+
if jsdoc:
|
71
|
+
lines.append(jsdoc)
|
72
|
+
|
73
|
+
# Function signature with optional client parameter
|
74
|
+
if param_info['func_params']:
|
75
|
+
lines.append(f"export async function {func_name}(")
|
76
|
+
lines.append(f" {param_info['func_params']},")
|
77
|
+
lines.append(f" client?: API")
|
78
|
+
lines.append(f"): Promise<{response_type}> {{")
|
79
|
+
else:
|
80
|
+
lines.append(f"export async function {func_name}(")
|
81
|
+
lines.append(f" client?: API")
|
82
|
+
lines.append(f"): Promise<{response_type}> {{")
|
83
|
+
|
84
|
+
# Get client instance (either passed or global)
|
85
|
+
lines.append(" const api = client || getAPIInstance()")
|
86
|
+
lines.append("")
|
87
|
+
|
88
|
+
# Function body - build API call
|
89
|
+
api_call_params = param_info['api_call_params']
|
90
|
+
# Replace API. with api.
|
91
|
+
api_call_instance = api_call.replace("API.", "api.")
|
92
|
+
|
93
|
+
if api_call_params:
|
94
|
+
lines.append(f" const response = await {api_call_instance}({api_call_params})")
|
95
|
+
else:
|
96
|
+
lines.append(f" const response = await {api_call_instance}()")
|
97
|
+
|
98
|
+
# Validation with Zod
|
99
|
+
if response_schema:
|
100
|
+
lines.append(f" return {response_schema}.parse(response)")
|
101
|
+
else:
|
102
|
+
lines.append(" return response")
|
103
|
+
|
104
|
+
lines.append("}")
|
105
|
+
|
106
|
+
return "\n".join(lines)
|
107
|
+
|
108
|
+
def _operation_to_function_name(self, operation: IROperationObject) -> str:
|
109
|
+
"""
|
110
|
+
Convert operation to function name.
|
111
|
+
|
112
|
+
Examples:
|
113
|
+
users_list (GET) -> getUsers
|
114
|
+
users_retrieve (GET) -> getUser
|
115
|
+
users_create (POST) -> createUser
|
116
|
+
users_update (PUT) -> updateUser
|
117
|
+
users_partial_update (PATCH) -> updateUser
|
118
|
+
users_destroy (DELETE) -> deleteUser
|
119
|
+
"""
|
120
|
+
# Remove tag prefix from operation_id
|
121
|
+
op_id = operation.operation_id
|
122
|
+
|
123
|
+
# Handle common patterns (remove only suffix, not all occurrences)
|
124
|
+
if op_id.endswith("_list"):
|
125
|
+
resource = op_id.removesuffix("_list")
|
126
|
+
return f"get{self._to_pascal_case(resource)}"
|
127
|
+
elif op_id.endswith("_retrieve"):
|
128
|
+
resource = op_id.removesuffix("_retrieve")
|
129
|
+
# Singular
|
130
|
+
return f"get{self._to_pascal_case(resource).rstrip('s')}"
|
131
|
+
elif op_id.endswith("_create"):
|
132
|
+
resource = op_id.removesuffix("_create")
|
133
|
+
return f"create{self._to_pascal_case(resource)}"
|
134
|
+
elif op_id.endswith("_partial_update"):
|
135
|
+
resource = op_id.removesuffix("_partial_update")
|
136
|
+
return f"partialUpdate{self._to_pascal_case(resource)}"
|
137
|
+
elif op_id.endswith("_update"):
|
138
|
+
resource = op_id.removesuffix("_update")
|
139
|
+
return f"update{self._to_pascal_case(resource)}"
|
140
|
+
elif op_id.endswith("_destroy"):
|
141
|
+
resource = op_id.removesuffix("_destroy")
|
142
|
+
return f"delete{self._to_pascal_case(resource)}"
|
143
|
+
else:
|
144
|
+
# Custom action - use operation_id as is
|
145
|
+
return self._to_camel_case(op_id)
|
146
|
+
|
147
|
+
def _to_pascal_case(self, snake_str: str) -> str:
|
148
|
+
"""Convert snake_case to PascalCase."""
|
149
|
+
return ''.join(word.capitalize() for word in snake_str.split('_'))
|
150
|
+
|
151
|
+
def _to_camel_case(self, snake_str: str) -> str:
|
152
|
+
"""Convert snake_case to camelCase."""
|
153
|
+
components = snake_str.split('_')
|
154
|
+
return components[0] + ''.join(x.capitalize() for x in components[1:])
|
155
|
+
|
156
|
+
def _get_param_structure(self, operation: IROperationObject) -> dict:
|
157
|
+
"""
|
158
|
+
Get structured parameter information for function generation.
|
159
|
+
|
160
|
+
Returns dict with:
|
161
|
+
- func_params: Function signature params (e.g., "slug: string, params?: { page?: number }")
|
162
|
+
- api_call_params: API call params (e.g., "slug, params" or "slug" or "params")
|
163
|
+
|
164
|
+
Examples:
|
165
|
+
GET /users/{id}/ -> {
|
166
|
+
func_params: "id: number",
|
167
|
+
api_call_params: "id"
|
168
|
+
}
|
169
|
+
|
170
|
+
GET /users/ with query params -> {
|
171
|
+
func_params: "params?: { page?: number }",
|
172
|
+
api_call_params: "params"
|
173
|
+
}
|
174
|
+
|
175
|
+
GET /users/{id}/ with query params -> {
|
176
|
+
func_params: "id: number, params?: { page?: number }",
|
177
|
+
api_call_params: "id, params"
|
178
|
+
}
|
179
|
+
|
180
|
+
POST /users/ -> {
|
181
|
+
func_params: "data: UserRequest",
|
182
|
+
api_call_params: "data"
|
183
|
+
}
|
184
|
+
|
185
|
+
POST /users/{id}/action/ -> {
|
186
|
+
func_params: "id: number, data: ActionRequest",
|
187
|
+
api_call_params: "id, data"
|
188
|
+
}
|
189
|
+
"""
|
190
|
+
func_params = []
|
191
|
+
api_call_params = []
|
192
|
+
|
193
|
+
# Path parameters (always passed individually)
|
194
|
+
if operation.path_parameters:
|
195
|
+
for param in operation.path_parameters:
|
196
|
+
param_type = self._map_param_type(param.schema_type)
|
197
|
+
func_params.append(f"{param.name}: {param_type}")
|
198
|
+
api_call_params.append(param.name)
|
199
|
+
|
200
|
+
# Query parameters (passed as params object, but unpacked when calling API)
|
201
|
+
if operation.query_parameters:
|
202
|
+
query_fields = []
|
203
|
+
# params is required only if all parameters are required
|
204
|
+
all_required = all(param.required for param in operation.query_parameters)
|
205
|
+
params_accessor = "params." if all_required else "params?."
|
206
|
+
|
207
|
+
for param in operation.query_parameters:
|
208
|
+
param_type = self._map_param_type(param.schema_type)
|
209
|
+
optional = "?" if not param.required else ""
|
210
|
+
query_fields.append(f"{param.name}{optional}: {param_type}")
|
211
|
+
# Unpack from params object when calling API
|
212
|
+
api_call_params.append(f"{params_accessor}{param.name}")
|
213
|
+
|
214
|
+
if query_fields:
|
215
|
+
params_optional = "" if all_required else "?"
|
216
|
+
func_params.append(f"params{params_optional}: {{ {'; '.join(query_fields)} }}")
|
217
|
+
|
218
|
+
# Request body (passed as data)
|
219
|
+
if operation.request_body:
|
220
|
+
schema_name = operation.request_body.schema_name
|
221
|
+
# Use schema only if it exists as a component (not inline)
|
222
|
+
if schema_name and schema_name in self.context.schemas:
|
223
|
+
body_type = schema_name
|
224
|
+
else:
|
225
|
+
body_type = "any"
|
226
|
+
func_params.append(f"data: {body_type}")
|
227
|
+
api_call_params.append("data")
|
228
|
+
|
229
|
+
return {
|
230
|
+
'func_params': ", ".join(func_params) if func_params else "",
|
231
|
+
'api_call_params': ", ".join(api_call_params) if api_call_params else ""
|
232
|
+
}
|
233
|
+
|
234
|
+
def _get_params_type(self, operation: IROperationObject) -> tuple[str, bool]:
|
235
|
+
"""
|
236
|
+
Get parameters type definition.
|
237
|
+
|
238
|
+
Returns:
|
239
|
+
(type_definition, has_params)
|
240
|
+
|
241
|
+
Examples:
|
242
|
+
("params?: { page?: number; page_size?: number }", True)
|
243
|
+
("id: number", True)
|
244
|
+
("", False)
|
245
|
+
"""
|
246
|
+
params = []
|
247
|
+
|
248
|
+
# Path parameters
|
249
|
+
if operation.path_parameters:
|
250
|
+
for param in operation.path_parameters:
|
251
|
+
param_type = self._map_param_type(param.schema_type)
|
252
|
+
params.append(f"{param.name}: {param_type}")
|
253
|
+
|
254
|
+
# Query parameters
|
255
|
+
if operation.query_parameters:
|
256
|
+
query_fields = []
|
257
|
+
all_required = all(param.required for param in operation.query_parameters)
|
258
|
+
|
259
|
+
for param in operation.query_parameters:
|
260
|
+
param_type = self._map_param_type(param.schema_type)
|
261
|
+
optional = "?" if not param.required else ""
|
262
|
+
query_fields.append(f"{param.name}{optional}: {param_type}")
|
263
|
+
|
264
|
+
if query_fields:
|
265
|
+
params_optional = "" if all_required else "?"
|
266
|
+
params.append(f"params{params_optional}: {{ {'; '.join(query_fields)} }}")
|
267
|
+
|
268
|
+
# Request body
|
269
|
+
if operation.request_body:
|
270
|
+
schema_name = operation.request_body.schema_name
|
271
|
+
# Use schema only if it exists as a component (not inline)
|
272
|
+
if schema_name and schema_name in self.context.schemas:
|
273
|
+
body_type = schema_name
|
274
|
+
else:
|
275
|
+
body_type = "any"
|
276
|
+
params.append(f"data: {body_type}")
|
277
|
+
|
278
|
+
if not params:
|
279
|
+
return ("", False)
|
280
|
+
|
281
|
+
return (", ".join(params), True)
|
282
|
+
|
283
|
+
def _map_param_type(self, param_type: str) -> str:
|
284
|
+
"""Map OpenAPI param type to TypeScript type."""
|
285
|
+
type_map = {
|
286
|
+
"integer": "number",
|
287
|
+
"number": "number",
|
288
|
+
"string": "string",
|
289
|
+
"boolean": "boolean",
|
290
|
+
"array": "any[]",
|
291
|
+
"object": "any",
|
292
|
+
}
|
293
|
+
return type_map.get(param_type, "any")
|
294
|
+
|
295
|
+
def _get_response_info(self, operation: IROperationObject) -> tuple[str, str | None]:
|
296
|
+
"""
|
297
|
+
Get response type and schema name.
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
(response_type, response_schema_name)
|
301
|
+
|
302
|
+
Examples:
|
303
|
+
("PaginatedUser", "PaginatedUserSchema")
|
304
|
+
("User", "UserSchema")
|
305
|
+
("void", None)
|
306
|
+
"""
|
307
|
+
# Get 2xx response
|
308
|
+
for status_code in [200, 201, 202, 204]:
|
309
|
+
if status_code in operation.responses:
|
310
|
+
response = operation.responses[status_code]
|
311
|
+
if response.schema_name:
|
312
|
+
schema_name = response.schema_name
|
313
|
+
return (schema_name, f"{schema_name}Schema")
|
314
|
+
|
315
|
+
# No response or void
|
316
|
+
if 204 in operation.responses or operation.http_method == "DELETE":
|
317
|
+
return ("void", None)
|
318
|
+
|
319
|
+
return ("any", None)
|
320
|
+
|
321
|
+
def _get_api_call(self, operation: IROperationObject) -> str:
|
322
|
+
"""
|
323
|
+
Get API client method call path.
|
324
|
+
|
325
|
+
Examples:
|
326
|
+
API.users.list
|
327
|
+
API.users.retrieve
|
328
|
+
API.posts.create
|
329
|
+
"""
|
330
|
+
# Get tag/resource name
|
331
|
+
tag = operation.tags[0] if operation.tags else "default"
|
332
|
+
tag_property = self.base.tag_to_property_name(tag)
|
333
|
+
|
334
|
+
# Get method name from operation_id
|
335
|
+
method_name = self.base.remove_tag_prefix(operation.operation_id, tag)
|
336
|
+
method_name = self._to_camel_case(method_name)
|
337
|
+
|
338
|
+
return f"API.{tag_property}.{method_name}"
|
339
|
+
|
340
|
+
def _generate_jsdoc(self, operation: IROperationObject, func_name: str) -> str:
|
341
|
+
"""Generate JSDoc comment for function."""
|
342
|
+
lines = ["/**"]
|
343
|
+
|
344
|
+
# Summary
|
345
|
+
if operation.summary:
|
346
|
+
lines.append(f" * {operation.summary}")
|
347
|
+
else:
|
348
|
+
lines.append(f" * {func_name}")
|
349
|
+
|
350
|
+
# Description
|
351
|
+
if operation.description:
|
352
|
+
lines.append(" *")
|
353
|
+
for desc_line in operation.description.split("\n"):
|
354
|
+
lines.append(f" * {desc_line}")
|
355
|
+
|
356
|
+
# HTTP method and path
|
357
|
+
lines.append(" *")
|
358
|
+
lines.append(f" * @method {operation.http_method}")
|
359
|
+
lines.append(f" * @path {operation.path}")
|
360
|
+
|
361
|
+
lines.append(" */")
|
362
|
+
return "\n".join(lines)
|
363
|
+
|
364
|
+
def generate_tag_fetchers_file(
|
365
|
+
self,
|
366
|
+
tag: str,
|
367
|
+
operations: list[IROperationObject],
|
368
|
+
) -> GeneratedFile:
|
369
|
+
"""
|
370
|
+
Generate fetchers file for a specific tag/resource.
|
371
|
+
|
372
|
+
Args:
|
373
|
+
tag: Tag name (e.g., "users", "posts")
|
374
|
+
operations: List of operations for this tag
|
375
|
+
|
376
|
+
Returns:
|
377
|
+
GeneratedFile with fetchers
|
378
|
+
"""
|
379
|
+
# Generate individual fetchers
|
380
|
+
fetchers = []
|
381
|
+
schema_names = set()
|
382
|
+
|
383
|
+
for operation in operations:
|
384
|
+
fetcher_code = self.generate_fetcher_function(operation)
|
385
|
+
fetchers.append(fetcher_code)
|
386
|
+
|
387
|
+
# Collect schema names
|
388
|
+
_, response_schema = self._get_response_info(operation)
|
389
|
+
if response_schema:
|
390
|
+
schema_name = response_schema.replace("Schema", "")
|
391
|
+
schema_names.add(schema_name)
|
392
|
+
|
393
|
+
# Add request body schemas (only if they exist as components)
|
394
|
+
if operation.request_body and operation.request_body.schema_name:
|
395
|
+
# Only add if schema exists in components (not inline)
|
396
|
+
if operation.request_body.schema_name in self.context.schemas:
|
397
|
+
schema_names.add(operation.request_body.schema_name)
|
398
|
+
|
399
|
+
# Get display name and folder name (use same naming as APIClient)
|
400
|
+
tag_display_name = self.base.tag_to_display_name(tag)
|
401
|
+
folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
|
402
|
+
|
403
|
+
# Render template
|
404
|
+
template = self.jinja_env.get_template("fetchers/fetchers.ts.jinja")
|
405
|
+
content = template.render(
|
406
|
+
tag_display_name=tag_display_name,
|
407
|
+
fetchers=fetchers,
|
408
|
+
has_schemas=bool(schema_names),
|
409
|
+
schema_names=sorted(schema_names),
|
410
|
+
has_client=True,
|
411
|
+
)
|
412
|
+
|
413
|
+
return GeneratedFile(
|
414
|
+
path=f"_utils/fetchers/{folder_name}.ts",
|
415
|
+
content=content,
|
416
|
+
description=f"Typed fetchers for {tag_display_name}",
|
417
|
+
)
|
418
|
+
|
419
|
+
def generate_fetchers_index_file(self, module_names: list[str]) -> GeneratedFile:
|
420
|
+
"""Generate index.ts for fetchers folder."""
|
421
|
+
template = self.jinja_env.get_template("fetchers/index.ts.jinja")
|
422
|
+
content = template.render(modules=sorted(module_names))
|
423
|
+
|
424
|
+
return GeneratedFile(
|
425
|
+
path="_utils/fetchers/index.ts",
|
426
|
+
content=content,
|
427
|
+
description="Fetchers index",
|
428
|
+
)
|
@@ -0,0 +1,207 @@
|
|
1
|
+
"""
|
2
|
+
TypeScript Files Generator - Generates utility files (index, http, errors, logger, etc.).
|
3
|
+
"""
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from jinja2 import Environment
|
8
|
+
from ...ir import IROperationObject
|
9
|
+
from ..base import GeneratedFile
|
10
|
+
|
11
|
+
|
12
|
+
class FilesGenerator:
|
13
|
+
"""Generates TypeScript utility files."""
|
14
|
+
|
15
|
+
def __init__(self, jinja_env: Environment, context, base):
|
16
|
+
self.jinja_env = jinja_env
|
17
|
+
self.context = context
|
18
|
+
self.base = base
|
19
|
+
self.openapi_schema = getattr(base, 'openapi_schema', None)
|
20
|
+
|
21
|
+
def generate_index_file(self):
|
22
|
+
"""Generate index.ts with exports."""
|
23
|
+
|
24
|
+
template = self.jinja_env.get_template('index.ts.jinja')
|
25
|
+
content = template.render(
|
26
|
+
has_enums=bool(self.base.get_enum_schemas())
|
27
|
+
)
|
28
|
+
|
29
|
+
return GeneratedFile(
|
30
|
+
path="index.ts",
|
31
|
+
content=content,
|
32
|
+
description="Module exports",
|
33
|
+
)
|
34
|
+
|
35
|
+
def generate_app_index_file(self, tag: str, operations: list[IROperationObject]):
|
36
|
+
"""Generate index.ts for a specific app."""
|
37
|
+
from ..base import GeneratedFile
|
38
|
+
|
39
|
+
template = self.jinja_env.get_template('app_index.ts.jinja')
|
40
|
+
content = template.render()
|
41
|
+
|
42
|
+
folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
|
43
|
+
return GeneratedFile(
|
44
|
+
path=f"{folder_name}/index.ts",
|
45
|
+
content=content,
|
46
|
+
description=f"Module exports for {tag}",
|
47
|
+
)
|
48
|
+
|
49
|
+
def generate_main_index_file(self):
|
50
|
+
"""Generate main index.ts with API class and JWT management."""
|
51
|
+
|
52
|
+
ops_by_tag = self.base.group_operations_by_tag()
|
53
|
+
tags = sorted(ops_by_tag.keys())
|
54
|
+
|
55
|
+
# Prepare tags data for template
|
56
|
+
tags_data = [
|
57
|
+
{
|
58
|
+
"class_name": self.base.tag_to_class_name(tag, suffix=""),
|
59
|
+
"property": self.base.tag_to_property_name(tag),
|
60
|
+
"slug": self.base.tag_and_app_to_folder_name(tag, ops_by_tag[tag]),
|
61
|
+
}
|
62
|
+
for tag in tags
|
63
|
+
]
|
64
|
+
|
65
|
+
# Check if we have enums
|
66
|
+
all_schemas = self.context.schemas
|
67
|
+
all_enums = self.base._collect_enums_from_schemas(all_schemas)
|
68
|
+
|
69
|
+
template = self.jinja_env.get_template('main_index.ts.jinja')
|
70
|
+
content = template.render(
|
71
|
+
api_title=self.context.openapi_info.title,
|
72
|
+
tags=tags_data,
|
73
|
+
has_enums=bool(all_enums),
|
74
|
+
generate_zod_schemas=getattr(self.base, 'generate_zod_schemas', False),
|
75
|
+
generate_fetchers=getattr(self.base, 'generate_fetchers', False),
|
76
|
+
generate_swr_hooks=getattr(self.base, 'generate_swr_hooks', False),
|
77
|
+
)
|
78
|
+
|
79
|
+
return GeneratedFile(
|
80
|
+
path="index.ts",
|
81
|
+
content=content,
|
82
|
+
description="Main index with API class and JWT management",
|
83
|
+
)
|
84
|
+
|
85
|
+
def generate_http_adapter_file(self):
|
86
|
+
"""Generate http.ts with HttpClient adapter interface."""
|
87
|
+
|
88
|
+
template = self.jinja_env.get_template('utils/http.ts.jinja')
|
89
|
+
content = template.render()
|
90
|
+
|
91
|
+
return GeneratedFile(
|
92
|
+
path="http.ts",
|
93
|
+
content=content,
|
94
|
+
description="HTTP client adapter interface and implementations",
|
95
|
+
)
|
96
|
+
|
97
|
+
def generate_errors_file(self):
|
98
|
+
"""Generate errors.ts with APIError class."""
|
99
|
+
|
100
|
+
template = self.jinja_env.get_template('utils/errors.ts.jinja')
|
101
|
+
content = template.render()
|
102
|
+
|
103
|
+
return GeneratedFile(
|
104
|
+
path="errors.ts",
|
105
|
+
content=content,
|
106
|
+
description="API error classes",
|
107
|
+
)
|
108
|
+
|
109
|
+
def generate_storage_file(self):
|
110
|
+
"""Generate storage.ts with StorageAdapter implementations."""
|
111
|
+
|
112
|
+
template = self.jinja_env.get_template('utils/storage.ts.jinja')
|
113
|
+
content = template.render()
|
114
|
+
|
115
|
+
return GeneratedFile(
|
116
|
+
path="storage.ts",
|
117
|
+
content=content,
|
118
|
+
description="Storage adapters for cross-platform support",
|
119
|
+
)
|
120
|
+
|
121
|
+
def generate_logger_file(self):
|
122
|
+
"""Generate logger.ts with Consola integration."""
|
123
|
+
|
124
|
+
template = self.jinja_env.get_template('utils/logger.ts.jinja')
|
125
|
+
content = template.render()
|
126
|
+
|
127
|
+
return GeneratedFile(
|
128
|
+
path="logger.ts",
|
129
|
+
content=content,
|
130
|
+
description="API Logger with Consola",
|
131
|
+
)
|
132
|
+
|
133
|
+
def generate_retry_file(self):
|
134
|
+
"""Generate retry.ts with p-retry integration."""
|
135
|
+
|
136
|
+
template = self.jinja_env.get_template('utils/retry.ts.jinja')
|
137
|
+
content = template.render()
|
138
|
+
|
139
|
+
return GeneratedFile(
|
140
|
+
path="retry.ts",
|
141
|
+
content=content,
|
142
|
+
description="Retry utilities with p-retry",
|
143
|
+
)
|
144
|
+
|
145
|
+
def generate_api_instance_file(self):
|
146
|
+
"""Generate api-instance.ts with global singleton."""
|
147
|
+
|
148
|
+
template = self.jinja_env.get_template('api_instance.ts.jinja')
|
149
|
+
content = template.render()
|
150
|
+
|
151
|
+
return GeneratedFile(
|
152
|
+
path="api-instance.ts",
|
153
|
+
content=content,
|
154
|
+
description="Global API singleton for universal configuration",
|
155
|
+
)
|
156
|
+
|
157
|
+
def generate_package_json_file(self, package_config: dict = None):
|
158
|
+
"""Generate package.json for npm publishing."""
|
159
|
+
if package_config is None:
|
160
|
+
package_config = {}
|
161
|
+
|
162
|
+
# Default configuration
|
163
|
+
defaults = {
|
164
|
+
"package_name": package_config.get("name", "api-client"),
|
165
|
+
"version": package_config.get("version", "1.0.0"),
|
166
|
+
"description": package_config.get("description") or f"Auto-generated TypeScript client for {self.context.openapi_info.title}",
|
167
|
+
"author": package_config.get("author"),
|
168
|
+
"license": package_config.get("license", "MIT"),
|
169
|
+
"repository_url": package_config.get("repository_url"),
|
170
|
+
"keywords": package_config.get("keywords", ["api", "client", "typescript", "openapi"]),
|
171
|
+
"private": package_config.get("private", False),
|
172
|
+
}
|
173
|
+
|
174
|
+
# Add Zod flag
|
175
|
+
defaults["generate_zod_schemas"] = self.base.generate_zod_schemas
|
176
|
+
|
177
|
+
template = self.jinja_env.get_template('package.json.jinja')
|
178
|
+
content = template.render(**defaults)
|
179
|
+
|
180
|
+
return GeneratedFile(
|
181
|
+
path="package.json",
|
182
|
+
content=content,
|
183
|
+
description="NPM package configuration",
|
184
|
+
)
|
185
|
+
|
186
|
+
def generate_tsconfig_file(self):
|
187
|
+
"""Generate tsconfig.json for TypeScript compilation."""
|
188
|
+
template = self.jinja_env.get_template('tsconfig.json.jinja')
|
189
|
+
content = template.render()
|
190
|
+
|
191
|
+
return GeneratedFile(
|
192
|
+
path="tsconfig.json",
|
193
|
+
content=content,
|
194
|
+
description="TypeScript compiler configuration",
|
195
|
+
)
|
196
|
+
|
197
|
+
def generate_schema_file(self):
|
198
|
+
"""Generate schema.ts with OpenAPI schema as const."""
|
199
|
+
|
200
|
+
template = self.jinja_env.get_template('utils/schema.ts.jinja')
|
201
|
+
content = template.render(schema=self.openapi_schema)
|
202
|
+
|
203
|
+
return GeneratedFile(
|
204
|
+
path="schema.ts",
|
205
|
+
content=content,
|
206
|
+
description="OpenAPI Schema",
|
207
|
+
)
|