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,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
|