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,432 @@
1
+ """
2
+ TypeScript Generator - Generates TypeScript client (Fetch API).
3
+
4
+ This generator creates a complete TypeScript API client from IR:
5
+ - TypeScript interfaces (Request/Response/Patch splits)
6
+ - Enum types from x-enum-varnames
7
+ - Fetch API for HTTP
8
+ - Django CSRF/session handling
9
+ - Type-safe
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import pathlib
15
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
16
+
17
+ from ..base import BaseGenerator, GeneratedFile
18
+ from ...ir import IROperationObject, IRSchemaObject
19
+
20
+ from .models_generator import ModelsGenerator
21
+ from .operations_generator import OperationsGenerator
22
+ from .client_generator import ClientGenerator
23
+ from .files_generator import FilesGenerator
24
+ from .schemas_generator import SchemasGenerator
25
+ from .fetchers_generator import FetchersGenerator
26
+ from .hooks_generator import HooksGenerator
27
+
28
+
29
+ class TypeScriptGenerator(BaseGenerator):
30
+ """
31
+ TypeScript client generator.
32
+
33
+ Generates:
34
+ - models.ts: TypeScript interfaces (User, UserRequest, PatchedUser)
35
+ - enums.ts: Enum types (StatusEnum, RoleEnum)
36
+ - client.ts: APIClient class with all operations
37
+ - index.ts: Module exports
38
+ """
39
+
40
+ def __init__(self, *args, **kwargs):
41
+ super().__init__(*args, **kwargs)
42
+
43
+ # Setup Jinja2 environment
44
+ templates_dir = pathlib.Path(__file__).parent / "templates"
45
+ self.jinja_env = Environment(
46
+ loader=FileSystemLoader(str(templates_dir)),
47
+ autoescape=select_autoescape(['html', 'xml']),
48
+ trim_blocks=True,
49
+ lstrip_blocks=True,
50
+ )
51
+
52
+ # Initialize sub-generators
53
+ self.models_gen = ModelsGenerator(self.jinja_env, self.context, self)
54
+ self.operations_gen = OperationsGenerator(self.jinja_env, self.context, self)
55
+ self.client_gen = ClientGenerator(self.jinja_env, self.context, self, self.operations_gen)
56
+ self.files_gen = FilesGenerator(self.jinja_env, self.context, self)
57
+ self.schemas_gen = SchemasGenerator(self.jinja_env, self.context, self)
58
+ self.fetchers_gen = FetchersGenerator(self.jinja_env, self.context, self)
59
+ self.hooks_gen = HooksGenerator(self.jinja_env, self.context, self)
60
+
61
+ def generate(self) -> list[GeneratedFile]:
62
+ """Generate all TypeScript client files."""
63
+ files = []
64
+
65
+ if self.client_structure == "namespaced":
66
+ # Generate per-app folders
67
+ ops_by_tag = self.group_operations_by_tag()
68
+
69
+ for tag, operations in sorted(ops_by_tag.items()):
70
+ # Generate app folder (models.ts, client.ts, index.ts)
71
+ files.extend(self._generate_app_folder(tag, operations))
72
+
73
+ # Generate shared enums.ts (Variant 2: all enums in root)
74
+ all_schemas = self.context.schemas
75
+ all_enums = self._collect_enums_from_schemas(all_schemas)
76
+ if all_enums:
77
+ files.append(self.models_gen.generate_shared_enums_file(all_enums))
78
+
79
+ # Generate main client.ts
80
+ files.append(self.client_gen.generate_main_client_file(ops_by_tag))
81
+
82
+ # Generate main index.ts
83
+ files.append(self.files_gen.generate_main_index_file())
84
+
85
+ # Generate http.ts with HttpClientAdapter
86
+ files.append(self.files_gen.generate_http_adapter_file())
87
+
88
+ # Generate errors.ts with APIError
89
+ files.append(self.files_gen.generate_errors_file())
90
+
91
+ # Generate storage.ts with StorageAdapter
92
+ files.append(self.files_gen.generate_storage_file())
93
+
94
+ # Generate logger.ts with Consola
95
+ files.append(self.files_gen.generate_logger_file())
96
+
97
+ # Generate retry.ts with p-retry
98
+ files.append(self.files_gen.generate_retry_file())
99
+
100
+ # Generate api-instance.ts singleton (needed for fetchers/hooks)
101
+ if self.generate_fetchers:
102
+ files.append(self.files_gen.generate_api_instance_file())
103
+
104
+ # Generate schema.ts with OpenAPI schema
105
+ if self.openapi_schema:
106
+ files.append(self.files_gen.generate_schema_file())
107
+
108
+ # Generate Zod schemas if requested
109
+ if self.generate_zod_schemas:
110
+ files.extend(self._generate_zod_schemas())
111
+
112
+ # Generate fetchers if requested
113
+ if self.generate_fetchers:
114
+ if not self.generate_zod_schemas:
115
+ print("⚠️ Warning: Fetchers require Zod schemas. Enable generate_zod_schemas.")
116
+ else:
117
+ files.extend(self._generate_fetchers())
118
+
119
+ # Generate SWR hooks if requested
120
+ if self.generate_swr_hooks:
121
+ if not self.generate_fetchers:
122
+ print("⚠️ Warning: SWR hooks require fetchers. Enable generate_fetchers.")
123
+ else:
124
+ files.extend(self._generate_swr_hooks())
125
+ else:
126
+ # Flat structure (original logic)
127
+ files.append(self.models_gen.generate_models_file())
128
+
129
+ enum_schemas = self.get_enum_schemas()
130
+ if enum_schemas:
131
+ files.append(self.models_gen.generate_enums_file())
132
+
133
+ files.append(self.client_gen.generate_client_file())
134
+ files.append(self.files_gen.generate_index_file())
135
+
136
+ # Generate storage.ts with StorageAdapter
137
+ files.append(self.files_gen.generate_storage_file())
138
+
139
+ # Generate logger.ts with Consola
140
+ files.append(self.files_gen.generate_logger_file())
141
+
142
+ # Generate retry.ts with p-retry
143
+ files.append(self.files_gen.generate_retry_file())
144
+
145
+ # Generate api-instance.ts singleton (needed for fetchers/hooks)
146
+ if self.generate_fetchers:
147
+ files.append(self.files_gen.generate_api_instance_file())
148
+
149
+ # Generate schema.ts with OpenAPI schema
150
+ if self.openapi_schema:
151
+ files.append(self.files_gen.generate_schema_file())
152
+
153
+ # Generate Zod schemas if requested
154
+ if self.generate_zod_schemas:
155
+ files.extend(self._generate_zod_schemas())
156
+
157
+ # Generate fetchers if requested
158
+ if self.generate_fetchers:
159
+ if not self.generate_zod_schemas:
160
+ print("⚠️ Warning: Fetchers require Zod schemas. Enable generate_zod_schemas.")
161
+ else:
162
+ files.extend(self._generate_fetchers())
163
+
164
+ # Generate SWR hooks if requested
165
+ if self.generate_swr_hooks:
166
+ if not self.generate_fetchers:
167
+ print("⚠️ Warning: SWR hooks require fetchers. Enable generate_fetchers.")
168
+ else:
169
+ files.extend(self._generate_swr_hooks())
170
+
171
+ # Generate package files if requested
172
+ if self.generate_package_files:
173
+ files.append(self.files_gen.generate_package_json_file(self.package_config))
174
+ files.append(self.files_gen.generate_tsconfig_file())
175
+
176
+ return files
177
+
178
+ # ===== Delegation Methods (for backward compatibility with tests) =====
179
+
180
+ def generate_schema(self, schema: IRSchemaObject) -> str:
181
+ """Generate TypeScript interface for schema."""
182
+ return self.models_gen.generate_schema(schema)
183
+
184
+ def generate_enum(self, schema: IRSchemaObject) -> str:
185
+ """Generate TypeScript enum from x-enum-varnames."""
186
+ return self.models_gen.generate_enum(schema)
187
+
188
+ def generate_operation(self, operation: IROperationObject, remove_tag_prefix: bool = False, in_subclient: bool = False) -> str:
189
+ """Generate async method for operation."""
190
+ return self.operations_gen.generate_operation(operation, remove_tag_prefix, in_subclient)
191
+
192
+ def _to_camel_case(self, snake_str: str) -> str:
193
+ """Convert snake_case to camelCase (delegate to operations generator)."""
194
+ return self.operations_gen._to_camel_case(snake_str)
195
+
196
+ # ===== Per-App Folder Generation (Namespaced Structure) =====
197
+
198
+ def _generate_app_folder(self, tag: str, operations: list[IROperationObject]) -> list[GeneratedFile]:
199
+ """Generate folder for a specific app (tag)."""
200
+ files = []
201
+
202
+ # Get schemas used by this app
203
+ app_schemas = self._get_schemas_for_operations(operations)
204
+
205
+ # Generate models.ts for this app
206
+ files.append(self.models_gen.generate_app_models_file(tag, app_schemas, operations))
207
+
208
+ # Generate client.ts for this app
209
+ files.append(self.client_gen.generate_app_client_file(tag, operations))
210
+
211
+ # Generate index.ts for this app
212
+ files.append(self.files_gen.generate_app_index_file(tag, operations))
213
+
214
+ return files
215
+
216
+ def _get_schemas_for_operations(self, operations: list[IROperationObject]) -> dict[str, IRSchemaObject]:
217
+ """
218
+ Get all schemas used by given operations.
219
+
220
+ This method recursively resolves all schema dependencies ($ref) to ensure
221
+ that nested schemas (e.g., APIKeyList referenced by PaginatedAPIKeyListList)
222
+ are included in the generated models file.
223
+ """
224
+ schemas = {}
225
+
226
+ for operation in operations:
227
+ # Request body schemas
228
+ if operation.request_body and operation.request_body.schema_name:
229
+ schema_name = operation.request_body.schema_name
230
+ if schema_name in self.context.schemas:
231
+ schemas[schema_name] = self.context.schemas[schema_name]
232
+
233
+ # Patch request body schemas
234
+ if operation.patch_request_body and operation.patch_request_body.schema_name:
235
+ schema_name = operation.patch_request_body.schema_name
236
+ if schema_name in self.context.schemas:
237
+ schemas[schema_name] = self.context.schemas[schema_name]
238
+
239
+ # Response schemas
240
+ for status_code, response in operation.responses.items():
241
+ if response.schema_name:
242
+ if response.schema_name in self.context.schemas:
243
+ schemas[response.schema_name] = self.context.schemas[response.schema_name]
244
+
245
+ # Recursively resolve all nested schema dependencies
246
+ schemas = self._resolve_nested_schemas(schemas)
247
+
248
+ return schemas
249
+
250
+ def _resolve_nested_schemas(self, initial_schemas: dict[str, IRSchemaObject]) -> dict[str, IRSchemaObject]:
251
+ """
252
+ Recursively resolve all nested schema dependencies ($ref).
253
+
254
+ This ensures that if SchemaA references SchemaB (e.g., via a property or array items),
255
+ SchemaB is also included in the output, even if it's not directly used in operations.
256
+
257
+ Example:
258
+ PaginatedAPIKeyListList has:
259
+ results: Array<APIKeyList> ← $ref to APIKeyList
260
+
261
+ This method will find APIKeyList and include it.
262
+
263
+ Args:
264
+ initial_schemas: Schemas directly used by operations
265
+
266
+ Returns:
267
+ All schemas including nested dependencies
268
+ """
269
+ resolved = dict(initial_schemas)
270
+ queue = list(initial_schemas.values())
271
+ seen = set(initial_schemas.keys())
272
+
273
+ while queue:
274
+ schema = queue.pop(0)
275
+
276
+ # Check properties for $ref and nested items
277
+ if schema.properties:
278
+ for prop in schema.properties.values():
279
+ # Direct $ref on property
280
+ if prop.ref and prop.ref not in seen:
281
+ if prop.ref in self.context.schemas:
282
+ resolved[prop.ref] = self.context.schemas[prop.ref]
283
+ queue.append(self.context.schemas[prop.ref])
284
+ seen.add(prop.ref)
285
+
286
+ # $ref inside array items (CRITICAL for PaginatedXList patterns!)
287
+ if prop.items and prop.items.ref:
288
+ if prop.items.ref not in seen:
289
+ if prop.items.ref in self.context.schemas:
290
+ resolved[prop.items.ref] = self.context.schemas[prop.items.ref]
291
+ queue.append(self.context.schemas[prop.items.ref])
292
+ seen.add(prop.items.ref)
293
+
294
+ # Check array items for $ref at schema level
295
+ if schema.items and schema.items.ref:
296
+ if schema.items.ref not in seen:
297
+ if schema.items.ref in self.context.schemas:
298
+ resolved[schema.items.ref] = self.context.schemas[schema.items.ref]
299
+ queue.append(self.context.schemas[schema.items.ref])
300
+ seen.add(schema.items.ref)
301
+
302
+ return resolved
303
+
304
+ # ===== Zod Schemas Generation =====
305
+
306
+ def _generate_zod_schemas(self) -> list[GeneratedFile]:
307
+ """
308
+ Generate Zod validation schemas for all models.
309
+
310
+ Creates:
311
+ - schemas/User.schema.ts
312
+ - schemas/UserRequest.schema.ts
313
+ - schemas/PaginatedUser.schema.ts
314
+ - schemas/index.ts
315
+ """
316
+ files = []
317
+ schema_names = []
318
+
319
+ # Get all schemas that should have Zod validation
320
+ all_schemas = {**self.context.schemas}
321
+
322
+ # Track refs to resolve dependencies
323
+ schema_refs = {} # schema_name -> set of referenced schemas
324
+ for schema_name, schema in all_schemas.items():
325
+ refs = self._get_schema_refs(schema)
326
+ schema_refs[schema_name] = refs
327
+
328
+ # Generate individual schema files
329
+ for schema_name, schema in sorted(all_schemas.items()):
330
+ # Skip enum schemas (they use z.nativeEnum from enums.ts)
331
+ if schema.enum:
332
+ continue
333
+
334
+ # Generate Zod schema file
335
+ refs = schema_refs.get(schema_name, set())
336
+ files.append(self.schemas_gen.generate_schema_file(schema, refs))
337
+ schema_names.append(schema_name)
338
+
339
+ # Generate index.ts
340
+ if schema_names:
341
+ files.append(self.schemas_gen.generate_schemas_index_file(schema_names))
342
+
343
+ return files
344
+
345
+ def _get_schema_refs(self, schema: IRSchemaObject) -> set[str]:
346
+ """
347
+ Get all schemas referenced by this schema.
348
+
349
+ Returns set of schema names that are directly referenced.
350
+ """
351
+ refs = set()
352
+
353
+ if schema.properties:
354
+ for prop in schema.properties.values():
355
+ if prop.ref and prop.ref in self.context.schemas:
356
+ # Don't include enum refs (they're handled separately)
357
+ if not self.context.schemas[prop.ref].enum:
358
+ refs.add(prop.ref)
359
+
360
+ if prop.items and prop.items.ref:
361
+ if prop.items.ref in self.context.schemas:
362
+ if not self.context.schemas[prop.items.ref].enum:
363
+ refs.add(prop.items.ref)
364
+
365
+ if schema.items and schema.items.ref:
366
+ if schema.items.ref in self.context.schemas:
367
+ if not self.context.schemas[schema.items.ref].enum:
368
+ refs.add(schema.items.ref)
369
+
370
+ return refs
371
+
372
+ # ===== Fetchers Generation =====
373
+
374
+ def _generate_fetchers(self) -> list[GeneratedFile]:
375
+ """
376
+ Generate typed fetcher functions for all operations.
377
+
378
+ Creates:
379
+ - _utils/fetchers/users.ts
380
+ - _utils/fetchers/posts.ts
381
+ - _utils/fetchers/index.ts
382
+ """
383
+ files = []
384
+ module_names = []
385
+
386
+ # Group operations by tag
387
+ ops_by_tag = self.group_operations_by_tag()
388
+
389
+ # Generate fetchers for each tag
390
+ for tag, operations in sorted(ops_by_tag.items()):
391
+ folder_name = self.tag_and_app_to_folder_name(tag, operations)
392
+
393
+ # Generate fetchers file for this tag
394
+ files.append(self.fetchers_gen.generate_tag_fetchers_file(tag, operations))
395
+ module_names.append(folder_name)
396
+
397
+ # Generate index.ts
398
+ if module_names:
399
+ files.append(self.fetchers_gen.generate_fetchers_index_file(module_names))
400
+
401
+ return files
402
+
403
+ # ===== SWR Hooks Generation =====
404
+
405
+ def _generate_swr_hooks(self) -> list[GeneratedFile]:
406
+ """
407
+ Generate SWR hooks for all operations.
408
+
409
+ Creates:
410
+ - _utils/hooks/shop_products.ts
411
+ - _utils/hooks/shop_orders.ts
412
+ - _utils/hooks/index.ts
413
+ """
414
+ files = []
415
+ module_names = []
416
+
417
+ # Group operations by tag
418
+ ops_by_tag = self.group_operations_by_tag()
419
+
420
+ # Generate hooks for each tag
421
+ for tag, operations in sorted(ops_by_tag.items()):
422
+ folder_name = self.tag_and_app_to_folder_name(tag, operations)
423
+
424
+ # Generate hooks file for this tag
425
+ files.append(self.hooks_gen.generate_tag_hooks_file(tag, operations))
426
+ module_names.append(folder_name)
427
+
428
+ # Generate index.ts
429
+ if module_names:
430
+ files.append(self.hooks_gen.generate_hooks_index_file(module_names))
431
+
432
+ return files