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.
Files changed (101) hide show
  1. django_cfg/core/generation/integration_generators/api.py +2 -1
  2. django_cfg/models/django/openapi.py +4 -80
  3. django_cfg/modules/django_client/core/archive/manager.py +2 -2
  4. django_cfg/modules/django_client/core/config/config.py +20 -0
  5. django_cfg/modules/django_client/core/config/service.py +1 -1
  6. django_cfg/modules/django_client/core/generator/__init__.py +4 -4
  7. django_cfg/modules/django_client/core/generator/base.py +71 -0
  8. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  9. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  10. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  11. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  12. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  13. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  14. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  15. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/api_wrapper.py.jinja +25 -2
  16. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client.py.jinja +24 -6
  17. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client_file.py.jinja +1 -0
  18. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/operation_method.py.jinja +3 -1
  19. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/sub_client.py.jinja +8 -1
  20. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  21. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  22. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  23. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/main_init.py.jinja +2 -0
  24. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enum_class.py.jinja +3 -1
  25. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/schema_class.py.jinja +3 -1
  26. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  27. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  28. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  29. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  30. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  31. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  32. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  33. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
  34. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  35. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  36. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  37. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  38. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/app_client.ts.jinja +1 -1
  39. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/client.ts.jinja +77 -1
  40. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/main_client_file.ts.jinja +1 -0
  41. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/sub_client.ts.jinja +3 -3
  42. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  43. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  44. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/main_index.ts.jinja +73 -11
  45. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  46. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  47. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  48. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  49. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/errors.ts.jinja +3 -1
  50. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/logger.ts.jinja +9 -1
  51. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  52. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/storage.ts.jinja +54 -10
  53. django_cfg/modules/django_client/management/commands/generate_client.py +5 -0
  54. django_cfg/modules/django_client/pytest.ini +30 -0
  55. django_cfg/modules/django_client/spectacular/__init__.py +3 -2
  56. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  57. django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
  58. django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
  59. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  60. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  61. django_cfg/modules/django_unfold/callbacks/main.py +6 -6
  62. django_cfg/pyproject.toml +1 -1
  63. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/METADATA +1 -1
  64. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/RECORD +99 -70
  65. django_cfg/modules/django_client/core/generator/python.py +0 -751
  66. django_cfg/modules/django_client/core/generator/typescript.py +0 -872
  67. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/__init__.py.jinja +0 -0
  68. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/app_init.py.jinja +0 -0
  69. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/app_client.py.jinja +0 -0
  70. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/flat_client.py.jinja +0 -0
  71. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client_file.py.jinja +0 -0
  72. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/app_models.py.jinja +0 -0
  73. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enums.py.jinja +0 -0
  74. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/models.py.jinja +0 -0
  75. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/logger.py.jinja +0 -0
  76. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/schema.py.jinja +0 -0
  77. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/app_index.ts.jinja +0 -0
  78. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/flat_client.ts.jinja +0 -0
  79. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/operation.ts.jinja +0 -0
  80. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client_file.ts.jinja +0 -0
  81. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/index.ts.jinja +0 -0
  82. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/app_models.ts.jinja +0 -0
  83. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/enums.ts.jinja +0 -0
  84. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/models.ts.jinja +0 -0
  85. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/http.ts.jinja +0 -0
  86. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/schema.ts.jinja +0 -0
  87. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  88. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  89. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  90. /django_cfg/{dashboard → modules/django_dashboard}/management/__init__.py +0 -0
  91. /django_cfg/{dashboard → modules/django_dashboard}/management/commands/__init__.py +0 -0
  92. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  93. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  94. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  95. /django_cfg/{dashboard → modules/django_dashboard}/sections/documentation.py +0 -0
  96. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  97. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  98. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  99. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
  100. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
  101. {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
+ )