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
@@ -1,872 +0,0 @@
|
|
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
|
-
|
21
|
-
class TypeScriptGenerator(BaseGenerator):
|
22
|
-
"""
|
23
|
-
TypeScript client generator.
|
24
|
-
|
25
|
-
Generates:
|
26
|
-
- models.ts: TypeScript interfaces (User, UserRequest, PatchedUser)
|
27
|
-
- enums.ts: Enum types (StatusEnum, RoleEnum)
|
28
|
-
- client.ts: APIClient class with all operations
|
29
|
-
- index.ts: Module exports
|
30
|
-
"""
|
31
|
-
|
32
|
-
def __init__(self, *args, **kwargs):
|
33
|
-
super().__init__(*args, **kwargs)
|
34
|
-
|
35
|
-
# Setup Jinja2 environment
|
36
|
-
templates_dir = pathlib.Path(__file__).parent / "templates"
|
37
|
-
self.jinja_env = Environment(
|
38
|
-
loader=FileSystemLoader(str(templates_dir)),
|
39
|
-
autoescape=select_autoescape(['html', 'xml']),
|
40
|
-
trim_blocks=True,
|
41
|
-
lstrip_blocks=True,
|
42
|
-
)
|
43
|
-
|
44
|
-
def generate(self) -> list[GeneratedFile]:
|
45
|
-
"""Generate all TypeScript client files."""
|
46
|
-
files = []
|
47
|
-
|
48
|
-
if self.client_structure == "namespaced":
|
49
|
-
# Generate per-app folders
|
50
|
-
ops_by_tag = self.group_operations_by_tag()
|
51
|
-
|
52
|
-
for tag, operations in sorted(ops_by_tag.items()):
|
53
|
-
# Generate app folder (models.ts, client.ts, index.ts)
|
54
|
-
files.extend(self._generate_app_folder(tag, operations))
|
55
|
-
|
56
|
-
# Generate shared enums.ts (Variant 2: all enums in root)
|
57
|
-
all_schemas = self.context.schemas
|
58
|
-
all_enums = self._collect_enums_from_schemas(all_schemas)
|
59
|
-
if all_enums:
|
60
|
-
files.append(self._generate_shared_enums_file(all_enums))
|
61
|
-
|
62
|
-
# Generate main client.ts
|
63
|
-
files.append(self._generate_main_client_file(ops_by_tag))
|
64
|
-
|
65
|
-
# Generate main index.ts
|
66
|
-
files.append(self._generate_main_index_file())
|
67
|
-
|
68
|
-
# Generate http.ts with HttpClientAdapter
|
69
|
-
files.append(self._generate_http_adapter_file())
|
70
|
-
|
71
|
-
# Generate errors.ts with APIError
|
72
|
-
files.append(self._generate_errors_file())
|
73
|
-
|
74
|
-
# Generate storage.ts with StorageAdapter
|
75
|
-
files.append(self._generate_storage_file())
|
76
|
-
|
77
|
-
# Generate logger.ts with Consola
|
78
|
-
files.append(self._generate_logger_file())
|
79
|
-
|
80
|
-
# Generate schema.ts with OpenAPI schema
|
81
|
-
if self.openapi_schema:
|
82
|
-
files.append(self._generate_schema_file())
|
83
|
-
else:
|
84
|
-
# Flat structure (original logic)
|
85
|
-
files.append(self._generate_models_file())
|
86
|
-
|
87
|
-
enum_schemas = self.get_enum_schemas()
|
88
|
-
if enum_schemas:
|
89
|
-
files.append(self._generate_enums_file())
|
90
|
-
|
91
|
-
files.append(self._generate_client_file())
|
92
|
-
files.append(self._generate_index_file())
|
93
|
-
|
94
|
-
# Generate storage.ts with StorageAdapter
|
95
|
-
files.append(self._generate_storage_file())
|
96
|
-
|
97
|
-
# Generate logger.ts with Consola
|
98
|
-
files.append(self._generate_logger_file())
|
99
|
-
|
100
|
-
# Generate schema.ts with OpenAPI schema
|
101
|
-
if self.openapi_schema:
|
102
|
-
files.append(self._generate_schema_file())
|
103
|
-
|
104
|
-
return files
|
105
|
-
|
106
|
-
# ===== Models Generation =====
|
107
|
-
|
108
|
-
def _generate_models_file(self) -> GeneratedFile:
|
109
|
-
"""Generate models.ts with all TypeScript interfaces."""
|
110
|
-
# Generate all schemas
|
111
|
-
schema_codes = []
|
112
|
-
|
113
|
-
# Response models first
|
114
|
-
for name, schema in self.get_response_schemas().items():
|
115
|
-
schema_codes.append(self.generate_schema(schema))
|
116
|
-
|
117
|
-
# Request models
|
118
|
-
for name, schema in self.get_request_schemas().items():
|
119
|
-
schema_codes.append(self.generate_schema(schema))
|
120
|
-
|
121
|
-
# Patch models
|
122
|
-
for name, schema in self.get_patch_schemas().items():
|
123
|
-
schema_codes.append(self.generate_schema(schema))
|
124
|
-
|
125
|
-
template = self.jinja_env.get_template('typescript/models/models.ts.jinja')
|
126
|
-
content = template.render(
|
127
|
-
has_enums=bool(self.get_enum_schemas()),
|
128
|
-
schemas=schema_codes
|
129
|
-
)
|
130
|
-
|
131
|
-
return GeneratedFile(
|
132
|
-
path="models.ts",
|
133
|
-
content=content,
|
134
|
-
description="TypeScript interfaces (Request/Response/Patch)",
|
135
|
-
)
|
136
|
-
|
137
|
-
def _generate_enums_file(self) -> GeneratedFile:
|
138
|
-
"""Generate enums.ts with all enum types (flat structure)."""
|
139
|
-
enum_codes = []
|
140
|
-
for name, schema in self.get_enum_schemas().items():
|
141
|
-
enum_codes.append(self.generate_enum(schema))
|
142
|
-
|
143
|
-
template = self.jinja_env.get_template('typescript/models/enums.ts.jinja')
|
144
|
-
content = template.render(enums=enum_codes)
|
145
|
-
|
146
|
-
return GeneratedFile(
|
147
|
-
path="enums.ts",
|
148
|
-
content=content,
|
149
|
-
description="Enum types from x-enum-varnames",
|
150
|
-
)
|
151
|
-
|
152
|
-
def _generate_shared_enums_file(self, enums: dict[str, IRSchemaObject]) -> GeneratedFile:
|
153
|
-
"""Generate shared enums.ts for namespaced structure (Variant 2)."""
|
154
|
-
enum_codes = []
|
155
|
-
for name, schema in enums.items():
|
156
|
-
enum_codes.append(self.generate_enum(schema))
|
157
|
-
|
158
|
-
template = self.jinja_env.get_template('typescript/models/enums.ts.jinja')
|
159
|
-
content = template.render(enums=enum_codes)
|
160
|
-
|
161
|
-
return GeneratedFile(
|
162
|
-
path="enums.ts",
|
163
|
-
content=content,
|
164
|
-
description="Shared enum types from x-enum-varnames",
|
165
|
-
)
|
166
|
-
|
167
|
-
# ===== Schema Generation =====
|
168
|
-
|
169
|
-
def generate_schema(self, schema: IRSchemaObject) -> str:
|
170
|
-
"""Generate TypeScript interface for schema."""
|
171
|
-
if schema.type != "object":
|
172
|
-
# For primitive types, skip (they'll be inlined)
|
173
|
-
return ""
|
174
|
-
|
175
|
-
# Interface comment
|
176
|
-
comment_lines = []
|
177
|
-
if schema.description:
|
178
|
-
comment_lines.extend(self.wrap_comment(schema.description, 76))
|
179
|
-
|
180
|
-
# Add metadata about model type
|
181
|
-
if schema.is_request_model:
|
182
|
-
comment_lines.append("")
|
183
|
-
comment_lines.append("Request model (no read-only fields).")
|
184
|
-
elif schema.is_patch_model:
|
185
|
-
comment_lines.append("")
|
186
|
-
comment_lines.append("PATCH model (all fields optional).")
|
187
|
-
elif schema.is_response_model:
|
188
|
-
comment_lines.append("")
|
189
|
-
comment_lines.append("Response model (includes read-only fields).")
|
190
|
-
|
191
|
-
comment = "/**\n * " + "\n * ".join(comment_lines) + "\n */" if comment_lines else None
|
192
|
-
|
193
|
-
# Fields
|
194
|
-
field_lines = []
|
195
|
-
|
196
|
-
for prop_name, prop_schema in schema.properties.items():
|
197
|
-
field_lines.append(self._generate_field(prop_name, prop_schema, schema.required))
|
198
|
-
|
199
|
-
# Build interface
|
200
|
-
lines = []
|
201
|
-
|
202
|
-
if comment:
|
203
|
-
lines.append(comment)
|
204
|
-
|
205
|
-
lines.append(f"export interface {schema.name} {{")
|
206
|
-
|
207
|
-
if field_lines:
|
208
|
-
for field_line in field_lines:
|
209
|
-
lines.append(self.indent(field_line, 2))
|
210
|
-
else:
|
211
|
-
# Empty interface
|
212
|
-
pass
|
213
|
-
|
214
|
-
lines.append("}")
|
215
|
-
|
216
|
-
return "\n".join(lines)
|
217
|
-
|
218
|
-
def _generate_field(
|
219
|
-
self,
|
220
|
-
name: str,
|
221
|
-
schema: IRSchemaObject,
|
222
|
-
required_fields: list[str],
|
223
|
-
) -> str:
|
224
|
-
"""
|
225
|
-
Generate TypeScript field definition.
|
226
|
-
|
227
|
-
Examples:
|
228
|
-
id: number;
|
229
|
-
username: string;
|
230
|
-
email?: string | null;
|
231
|
-
status: Enums.StatusEnum;
|
232
|
-
"""
|
233
|
-
# Check if this field is an enum
|
234
|
-
if schema.enum and schema.name:
|
235
|
-
# Use enum type from shared enums
|
236
|
-
ts_type = f"Enums.{schema.name}"
|
237
|
-
if schema.nullable:
|
238
|
-
ts_type = f"{ts_type} | null"
|
239
|
-
# Check if this field is a reference to an enum (via $ref)
|
240
|
-
elif schema.ref and schema.ref in self.context.schemas:
|
241
|
-
ref_schema = self.context.schemas[schema.ref]
|
242
|
-
if ref_schema.enum:
|
243
|
-
# This is a reference to an enum component
|
244
|
-
ts_type = f"Enums.{schema.ref}"
|
245
|
-
if schema.nullable:
|
246
|
-
ts_type = f"{ts_type} | null"
|
247
|
-
else:
|
248
|
-
# Regular reference
|
249
|
-
ts_type = schema.typescript_type
|
250
|
-
else:
|
251
|
-
# Get TypeScript type
|
252
|
-
ts_type = schema.typescript_type
|
253
|
-
|
254
|
-
# Check if required
|
255
|
-
is_required = name in required_fields
|
256
|
-
|
257
|
-
# Optional marker
|
258
|
-
optional_marker = "" if is_required else "?"
|
259
|
-
|
260
|
-
# Comment
|
261
|
-
if schema.description:
|
262
|
-
return f"/** {schema.description} */\n{name}{optional_marker}: {ts_type};"
|
263
|
-
|
264
|
-
return f"{name}{optional_marker}: {ts_type};"
|
265
|
-
|
266
|
-
def generate_enum(self, schema: IRSchemaObject) -> str:
|
267
|
-
"""Generate TypeScript enum from x-enum-varnames."""
|
268
|
-
# Enum comment
|
269
|
-
comment_lines = []
|
270
|
-
if schema.description:
|
271
|
-
comment_lines.extend(self.wrap_comment(schema.description, 76))
|
272
|
-
|
273
|
-
comment = "/**\n * " + "\n * ".join(comment_lines) + "\n */" if comment_lines else None
|
274
|
-
|
275
|
-
# Enum members
|
276
|
-
member_lines = []
|
277
|
-
for var_name, value in zip(schema.enum_var_names, schema.enum):
|
278
|
-
if isinstance(value, str):
|
279
|
-
member_lines.append(f'{var_name} = "{value}",')
|
280
|
-
else:
|
281
|
-
member_lines.append(f"{var_name} = {value},")
|
282
|
-
|
283
|
-
# Build enum
|
284
|
-
lines = []
|
285
|
-
|
286
|
-
if comment:
|
287
|
-
lines.append(comment)
|
288
|
-
|
289
|
-
lines.append(f"export enum {schema.name} {{")
|
290
|
-
|
291
|
-
for member_line in member_lines:
|
292
|
-
lines.append(self.indent(member_line, 2))
|
293
|
-
|
294
|
-
lines.append("}")
|
295
|
-
|
296
|
-
return "\n".join(lines)
|
297
|
-
|
298
|
-
# ===== Client Generation =====
|
299
|
-
|
300
|
-
def _generate_client_file(self) -> GeneratedFile:
|
301
|
-
"""Generate client.ts with APIClient class."""
|
302
|
-
# Client class
|
303
|
-
client_code = self._generate_client_class()
|
304
|
-
|
305
|
-
template = self.jinja_env.get_template('typescript/client_file.ts.jinja')
|
306
|
-
content = template.render(
|
307
|
-
has_enums=bool(self.get_enum_schemas()),
|
308
|
-
client_code=client_code
|
309
|
-
)
|
310
|
-
|
311
|
-
return GeneratedFile(
|
312
|
-
path="client.ts",
|
313
|
-
content=content,
|
314
|
-
description="APIClient with HTTP adapter and error handling",
|
315
|
-
)
|
316
|
-
|
317
|
-
def _generate_client_class(self) -> str:
|
318
|
-
"""Generate APIClient class."""
|
319
|
-
if self.client_structure == "namespaced":
|
320
|
-
return self._generate_namespaced_client()
|
321
|
-
else:
|
322
|
-
return self._generate_flat_client()
|
323
|
-
|
324
|
-
def _generate_flat_client(self) -> str:
|
325
|
-
"""Generate flat APIClient (all methods in one class)."""
|
326
|
-
# Generate all operation methods
|
327
|
-
method_codes = []
|
328
|
-
for op_id, operation in self.context.operations.items():
|
329
|
-
method_codes.append(self.generate_operation(operation))
|
330
|
-
|
331
|
-
template = self.jinja_env.get_template('typescript/client/flat_client.ts.jinja')
|
332
|
-
return template.render(
|
333
|
-
api_title=self.context.openapi_info.title,
|
334
|
-
operations=method_codes
|
335
|
-
)
|
336
|
-
|
337
|
-
def _generate_namespaced_client(self) -> str:
|
338
|
-
"""Generate namespaced APIClient (sub-clients per tag)."""
|
339
|
-
# Group operations by tag (using base class method)
|
340
|
-
ops_by_tag = self.group_operations_by_tag()
|
341
|
-
|
342
|
-
# Generate sub-client classes
|
343
|
-
sub_client_classes = []
|
344
|
-
for tag, operations in sorted(ops_by_tag.items()):
|
345
|
-
sub_client_classes.append(self._generate_sub_client_class(tag, operations))
|
346
|
-
|
347
|
-
sub_clients_code = "\n\n".join(sub_client_classes)
|
348
|
-
|
349
|
-
# Generate main APIClient
|
350
|
-
main_client_code = self._generate_main_client_class(list(ops_by_tag.keys()))
|
351
|
-
|
352
|
-
return f"{sub_clients_code}\n\n{main_client_code}"
|
353
|
-
|
354
|
-
def _generate_sub_client_class(self, tag: str, operations: list) -> str:
|
355
|
-
"""Generate sub-client class for a specific tag."""
|
356
|
-
class_name = self.tag_to_class_name(tag)
|
357
|
-
|
358
|
-
# Generate methods for this tag
|
359
|
-
method_codes = []
|
360
|
-
for operation in operations:
|
361
|
-
method_codes.append(self.generate_operation(operation, remove_tag_prefix=True, in_subclient=True))
|
362
|
-
|
363
|
-
template = self.jinja_env.get_template('typescript/client/sub_client.ts.jinja')
|
364
|
-
return template.render(
|
365
|
-
tag=self.tag_to_display_name(tag),
|
366
|
-
class_name=class_name,
|
367
|
-
operations=method_codes
|
368
|
-
)
|
369
|
-
|
370
|
-
def _generate_main_client_class(self, ops_by_tag: dict) -> str:
|
371
|
-
"""Generate main APIClient with sub-clients."""
|
372
|
-
tags = sorted(ops_by_tag.keys())
|
373
|
-
|
374
|
-
# Prepare data for template
|
375
|
-
tags_data = [
|
376
|
-
{
|
377
|
-
"class_name": self.tag_to_class_name(tag),
|
378
|
-
"property": self.tag_to_property_name(tag),
|
379
|
-
"slug": self.tag_and_app_to_folder_name(tag, ops_by_tag[tag]),
|
380
|
-
}
|
381
|
-
for tag in tags
|
382
|
-
]
|
383
|
-
|
384
|
-
template = self.jinja_env.get_template('typescript/client/client.ts.jinja')
|
385
|
-
return template.render(
|
386
|
-
sub_clients=True,
|
387
|
-
include_imports=False, # Imports already in main_client_file.ts.jinja
|
388
|
-
tags=tags_data,
|
389
|
-
info={"title": self.context.openapi_info.title},
|
390
|
-
)
|
391
|
-
|
392
|
-
def generate_operation(self, operation: IROperationObject, remove_tag_prefix: bool = False, in_subclient: bool = False) -> str:
|
393
|
-
"""Generate async method for operation."""
|
394
|
-
# Get method name
|
395
|
-
operation_id = operation.operation_id
|
396
|
-
if remove_tag_prefix and operation.tags:
|
397
|
-
# Remove tag prefix using base class method
|
398
|
-
tag = operation.tags[0]
|
399
|
-
operation_id = self.remove_tag_prefix(operation_id, tag)
|
400
|
-
|
401
|
-
# Convert snake_case to camelCase
|
402
|
-
method_name = self._to_camel_case(operation_id)
|
403
|
-
|
404
|
-
# Request method prefix
|
405
|
-
request_prefix = "this.client" if in_subclient else "this"
|
406
|
-
|
407
|
-
# Method parameters
|
408
|
-
params = []
|
409
|
-
|
410
|
-
# Add path parameters
|
411
|
-
for param in operation.path_parameters:
|
412
|
-
param_type = self._map_param_type(param.schema_type)
|
413
|
-
params.append(f"{param.name}: {param_type}")
|
414
|
-
|
415
|
-
# Check if this is a file upload operation
|
416
|
-
is_multipart = (
|
417
|
-
operation.request_body
|
418
|
-
and operation.request_body.content_type == "multipart/form-data"
|
419
|
-
)
|
420
|
-
|
421
|
-
# Add request body parameter
|
422
|
-
if operation.request_body:
|
423
|
-
if is_multipart:
|
424
|
-
# For multipart, get schema properties and add as individual File parameters
|
425
|
-
schema_name = operation.request_body.schema_name
|
426
|
-
if schema_name in self.context.schemas:
|
427
|
-
schema = self.context.schemas[schema_name]
|
428
|
-
for prop_name, prop in schema.properties.items():
|
429
|
-
# Check if it's a file field (format: binary)
|
430
|
-
if prop.format == "binary":
|
431
|
-
params.append(f"{prop_name}: File | Blob")
|
432
|
-
else:
|
433
|
-
# Regular field in multipart
|
434
|
-
prop_type = self._map_param_type(prop.type)
|
435
|
-
if prop_name in schema.required:
|
436
|
-
params.append(f"{prop_name}: {prop_type}")
|
437
|
-
else:
|
438
|
-
params.append(f"{prop_name}?: {prop_type}")
|
439
|
-
else:
|
440
|
-
# JSON request body
|
441
|
-
params.append(f"data: Models.{operation.request_body.schema_name}")
|
442
|
-
elif operation.patch_request_body:
|
443
|
-
params.append(f"data?: Models.{operation.patch_request_body.schema_name}")
|
444
|
-
|
445
|
-
# Add query parameters
|
446
|
-
for param in operation.query_parameters:
|
447
|
-
param_type = self._map_param_type(param.schema_type)
|
448
|
-
if not param.required:
|
449
|
-
param_type = f"{param_type} | null"
|
450
|
-
params.append(f"{param.name}?: {param_type}")
|
451
|
-
else:
|
452
|
-
params.append(f"{param.name}: {param_type}")
|
453
|
-
|
454
|
-
# Return type
|
455
|
-
primary_response = operation.primary_success_response
|
456
|
-
if primary_response and primary_response.schema_name:
|
457
|
-
if operation.is_list_operation:
|
458
|
-
return_type = f"Models.{primary_response.schema_name}[]"
|
459
|
-
else:
|
460
|
-
return_type = f"Models.{primary_response.schema_name}"
|
461
|
-
else:
|
462
|
-
return_type = "void"
|
463
|
-
|
464
|
-
signature = f"async {method_name}({', '.join(params)}): Promise<{return_type}> {{"
|
465
|
-
|
466
|
-
# Comment
|
467
|
-
comment_lines = []
|
468
|
-
if operation.summary:
|
469
|
-
comment_lines.append(operation.summary)
|
470
|
-
if operation.description:
|
471
|
-
if comment_lines:
|
472
|
-
comment_lines.append("")
|
473
|
-
comment_lines.extend(self.wrap_comment(operation.description, 72))
|
474
|
-
|
475
|
-
comment = "/**\n * " + "\n * ".join(comment_lines) + "\n */" if comment_lines else None
|
476
|
-
|
477
|
-
# Method body
|
478
|
-
body_lines = []
|
479
|
-
|
480
|
-
# Build path
|
481
|
-
path_expr = f'"{operation.path}"'
|
482
|
-
if operation.path_parameters:
|
483
|
-
# Replace {id} with ${id}
|
484
|
-
path_with_vars = operation.path
|
485
|
-
for param in operation.path_parameters:
|
486
|
-
path_with_vars = path_with_vars.replace(f"{{{param.name}}}", f"${{{param.name}}}")
|
487
|
-
path_expr = f'`{path_with_vars}`'
|
488
|
-
|
489
|
-
# Build request options
|
490
|
-
request_opts = []
|
491
|
-
|
492
|
-
# Query params
|
493
|
-
if operation.query_parameters:
|
494
|
-
query_items = [f"{param.name}" for param in operation.query_parameters]
|
495
|
-
query_dict = "{ " + ", ".join(query_items) + " }"
|
496
|
-
request_opts.append(f"params: {query_dict}")
|
497
|
-
|
498
|
-
# Body / FormData
|
499
|
-
if operation.request_body or operation.patch_request_body:
|
500
|
-
if is_multipart and operation.request_body:
|
501
|
-
# Build FormData for multipart upload
|
502
|
-
schema_name = operation.request_body.schema_name
|
503
|
-
if schema_name in self.context.schemas:
|
504
|
-
schema = self.context.schemas[schema_name]
|
505
|
-
body_lines.append("const formData = new FormData();")
|
506
|
-
for prop_name, prop in schema.properties.items():
|
507
|
-
if prop.format == "binary":
|
508
|
-
# Append file
|
509
|
-
body_lines.append(f"formData.append('{prop_name}', {prop_name});")
|
510
|
-
elif prop_name in schema.required or True: # Append all non-undefined fields
|
511
|
-
# Append other fields (wrap in if check for optional)
|
512
|
-
if prop_name not in schema.required:
|
513
|
-
body_lines.append(f"if ({prop_name} !== undefined) formData.append('{prop_name}', String({prop_name}));")
|
514
|
-
else:
|
515
|
-
body_lines.append(f"formData.append('{prop_name}', String({prop_name}));")
|
516
|
-
request_opts.append("formData")
|
517
|
-
else:
|
518
|
-
# JSON body
|
519
|
-
request_opts.append("body: data")
|
520
|
-
|
521
|
-
# Make request
|
522
|
-
if request_opts:
|
523
|
-
request_line = f"const response = await {request_prefix}.request<{return_type}>('{operation.http_method}', {path_expr}, {{ {', '.join(request_opts)} }});"
|
524
|
-
else:
|
525
|
-
request_line = f"const response = await {request_prefix}.request<{return_type}>('{operation.http_method}', {path_expr});"
|
526
|
-
|
527
|
-
body_lines.append(request_line)
|
528
|
-
|
529
|
-
# Handle response
|
530
|
-
if operation.is_list_operation and primary_response:
|
531
|
-
# Extract results from paginated response
|
532
|
-
body_lines.append("return (response as any).results || [];")
|
533
|
-
elif return_type != "void":
|
534
|
-
body_lines.append("return response;")
|
535
|
-
else:
|
536
|
-
body_lines.append("return;")
|
537
|
-
|
538
|
-
# Build method
|
539
|
-
lines = []
|
540
|
-
|
541
|
-
if comment:
|
542
|
-
lines.append(comment)
|
543
|
-
|
544
|
-
lines.append(signature)
|
545
|
-
|
546
|
-
for line in body_lines:
|
547
|
-
lines.append(self.indent(line, 2))
|
548
|
-
|
549
|
-
lines.append("}")
|
550
|
-
|
551
|
-
return "\n".join(lines)
|
552
|
-
|
553
|
-
def _map_param_type(self, schema_type: str) -> str:
|
554
|
-
"""Map parameter schema type to TypeScript type."""
|
555
|
-
type_map = {
|
556
|
-
"string": "string",
|
557
|
-
"integer": "number",
|
558
|
-
"number": "number",
|
559
|
-
"boolean": "boolean",
|
560
|
-
"array": "any[]",
|
561
|
-
}
|
562
|
-
return type_map.get(schema_type, "any")
|
563
|
-
|
564
|
-
def _to_camel_case(self, snake_str: str) -> str:
|
565
|
-
"""
|
566
|
-
Convert snake_case to camelCase.
|
567
|
-
|
568
|
-
Examples:
|
569
|
-
>>> self._to_camel_case("users_list")
|
570
|
-
'usersList'
|
571
|
-
>>> self._to_camel_case("users_partial_update")
|
572
|
-
'usersPartialUpdate'
|
573
|
-
"""
|
574
|
-
components = snake_str.split("_")
|
575
|
-
return components[0] + "".join(x.title() for x in components[1:])
|
576
|
-
|
577
|
-
# ===== Index File =====
|
578
|
-
|
579
|
-
def _generate_index_file(self) -> GeneratedFile:
|
580
|
-
"""Generate index.ts with exports."""
|
581
|
-
template = self.jinja_env.get_template('typescript/index.ts.jinja')
|
582
|
-
content = template.render(
|
583
|
-
has_enums=bool(self.get_enum_schemas())
|
584
|
-
)
|
585
|
-
|
586
|
-
return GeneratedFile(
|
587
|
-
path="index.ts",
|
588
|
-
content=content,
|
589
|
-
description="Module exports",
|
590
|
-
)
|
591
|
-
|
592
|
-
# ===== Per-App Folder Generation (Namespaced Structure) =====
|
593
|
-
|
594
|
-
def _generate_app_folder(self, tag: str, operations: list[IROperationObject]) -> list[GeneratedFile]:
|
595
|
-
"""Generate folder for a specific app (tag)."""
|
596
|
-
files = []
|
597
|
-
|
598
|
-
# Get schemas used by this app
|
599
|
-
app_schemas = self._get_schemas_for_operations(operations)
|
600
|
-
|
601
|
-
# Generate models.ts for this app
|
602
|
-
files.append(self._generate_app_models_file(tag, app_schemas, operations))
|
603
|
-
|
604
|
-
# Generate client.ts for this app
|
605
|
-
files.append(self._generate_app_client_file(tag, operations))
|
606
|
-
|
607
|
-
# Generate index.ts for this app
|
608
|
-
files.append(self._generate_app_index_file(tag, operations))
|
609
|
-
|
610
|
-
return files
|
611
|
-
|
612
|
-
def _get_schemas_for_operations(self, operations: list[IROperationObject]) -> dict[str, IRSchemaObject]:
|
613
|
-
"""
|
614
|
-
Get all schemas used by given operations.
|
615
|
-
|
616
|
-
This method recursively resolves all schema dependencies ($ref) to ensure
|
617
|
-
that nested schemas (e.g., APIKeyList referenced by PaginatedAPIKeyListList)
|
618
|
-
are included in the generated models file.
|
619
|
-
"""
|
620
|
-
schemas = {}
|
621
|
-
|
622
|
-
for operation in operations:
|
623
|
-
# Request body schemas
|
624
|
-
if operation.request_body and operation.request_body.schema_name:
|
625
|
-
schema_name = operation.request_body.schema_name
|
626
|
-
if schema_name in self.context.schemas:
|
627
|
-
schemas[schema_name] = self.context.schemas[schema_name]
|
628
|
-
|
629
|
-
# Patch request body schemas
|
630
|
-
if operation.patch_request_body and operation.patch_request_body.schema_name:
|
631
|
-
schema_name = operation.patch_request_body.schema_name
|
632
|
-
if schema_name in self.context.schemas:
|
633
|
-
schemas[schema_name] = self.context.schemas[schema_name]
|
634
|
-
|
635
|
-
# Response schemas
|
636
|
-
for status_code, response in operation.responses.items():
|
637
|
-
if response.schema_name:
|
638
|
-
if response.schema_name in self.context.schemas:
|
639
|
-
schemas[response.schema_name] = self.context.schemas[response.schema_name]
|
640
|
-
|
641
|
-
# Recursively resolve all nested schema dependencies
|
642
|
-
schemas = self._resolve_nested_schemas(schemas)
|
643
|
-
|
644
|
-
return schemas
|
645
|
-
|
646
|
-
def _resolve_nested_schemas(self, initial_schemas: dict[str, IRSchemaObject]) -> dict[str, IRSchemaObject]:
|
647
|
-
"""
|
648
|
-
Recursively resolve all nested schema dependencies ($ref).
|
649
|
-
|
650
|
-
This ensures that if SchemaA references SchemaB (e.g., via a property or array items),
|
651
|
-
SchemaB is also included in the output, even if it's not directly used in operations.
|
652
|
-
|
653
|
-
Example:
|
654
|
-
PaginatedAPIKeyListList has:
|
655
|
-
results: Array<APIKeyList> ← $ref to APIKeyList
|
656
|
-
|
657
|
-
This method will find APIKeyList and include it.
|
658
|
-
|
659
|
-
Args:
|
660
|
-
initial_schemas: Schemas directly used by operations
|
661
|
-
|
662
|
-
Returns:
|
663
|
-
All schemas including nested dependencies
|
664
|
-
"""
|
665
|
-
resolved = dict(initial_schemas)
|
666
|
-
queue = list(initial_schemas.values())
|
667
|
-
seen = set(initial_schemas.keys())
|
668
|
-
|
669
|
-
while queue:
|
670
|
-
schema = queue.pop(0)
|
671
|
-
|
672
|
-
# Check properties for $ref and nested items
|
673
|
-
if schema.properties:
|
674
|
-
for prop in schema.properties.values():
|
675
|
-
# Direct $ref on property
|
676
|
-
if prop.ref and prop.ref not in seen:
|
677
|
-
if prop.ref in self.context.schemas:
|
678
|
-
resolved[prop.ref] = self.context.schemas[prop.ref]
|
679
|
-
queue.append(self.context.schemas[prop.ref])
|
680
|
-
seen.add(prop.ref)
|
681
|
-
|
682
|
-
# $ref inside array items (CRITICAL for PaginatedXList patterns!)
|
683
|
-
if prop.items and prop.items.ref:
|
684
|
-
if prop.items.ref not in seen:
|
685
|
-
if prop.items.ref in self.context.schemas:
|
686
|
-
resolved[prop.items.ref] = self.context.schemas[prop.items.ref]
|
687
|
-
queue.append(self.context.schemas[prop.items.ref])
|
688
|
-
seen.add(prop.items.ref)
|
689
|
-
|
690
|
-
# Check array items for $ref at schema level
|
691
|
-
if schema.items and schema.items.ref:
|
692
|
-
if schema.items.ref not in seen:
|
693
|
-
if schema.items.ref in self.context.schemas:
|
694
|
-
resolved[schema.items.ref] = self.context.schemas[schema.items.ref]
|
695
|
-
queue.append(self.context.schemas[schema.items.ref])
|
696
|
-
seen.add(schema.items.ref)
|
697
|
-
|
698
|
-
return resolved
|
699
|
-
|
700
|
-
def _generate_app_models_file(self, tag: str, schemas: dict[str, IRSchemaObject], operations: list[IROperationObject]) -> GeneratedFile:
|
701
|
-
"""Generate models.ts for a specific app."""
|
702
|
-
# Check if we have enums in schemas
|
703
|
-
app_enums = self._collect_enums_from_schemas(schemas)
|
704
|
-
has_enums = len(app_enums) > 0
|
705
|
-
|
706
|
-
# Generate schemas
|
707
|
-
schema_codes = []
|
708
|
-
for name, schema in schemas.items():
|
709
|
-
schema_codes.append(self.generate_schema(schema))
|
710
|
-
|
711
|
-
template = self.jinja_env.get_template('typescript/models/app_models.ts.jinja')
|
712
|
-
content = template.render(
|
713
|
-
has_enums=has_enums,
|
714
|
-
schemas=schema_codes
|
715
|
-
)
|
716
|
-
|
717
|
-
folder_name = self.tag_and_app_to_folder_name(tag, operations)
|
718
|
-
return GeneratedFile(
|
719
|
-
path=f"{folder_name}/models.ts",
|
720
|
-
content=content,
|
721
|
-
description=f"TypeScript interfaces for {tag}",
|
722
|
-
)
|
723
|
-
|
724
|
-
def _generate_app_client_file(self, tag: str, operations: list[IROperationObject]) -> GeneratedFile:
|
725
|
-
"""Generate client.ts for a specific app."""
|
726
|
-
class_name = self.tag_to_class_name(tag)
|
727
|
-
|
728
|
-
# Generate methods
|
729
|
-
method_codes = []
|
730
|
-
for operation in operations:
|
731
|
-
method_codes.append(self.generate_operation(operation, remove_tag_prefix=True, in_subclient=True))
|
732
|
-
|
733
|
-
template = self.jinja_env.get_template('typescript/client/app_client.ts.jinja')
|
734
|
-
content = template.render(
|
735
|
-
tag=self.tag_to_display_name(tag),
|
736
|
-
class_name=class_name,
|
737
|
-
operations=method_codes
|
738
|
-
)
|
739
|
-
|
740
|
-
folder_name = self.tag_and_app_to_folder_name(tag, operations)
|
741
|
-
return GeneratedFile(
|
742
|
-
path=f"{folder_name}/client.ts",
|
743
|
-
content=content,
|
744
|
-
description=f"API client for {tag}",
|
745
|
-
)
|
746
|
-
|
747
|
-
def _generate_app_index_file(self, tag: str, operations: list[IROperationObject]) -> GeneratedFile:
|
748
|
-
"""Generate index.ts for a specific app."""
|
749
|
-
template = self.jinja_env.get_template('typescript/app_index.ts.jinja')
|
750
|
-
content = template.render()
|
751
|
-
|
752
|
-
folder_name = self.tag_and_app_to_folder_name(tag, operations)
|
753
|
-
return GeneratedFile(
|
754
|
-
path=f"{folder_name}/index.ts",
|
755
|
-
content=content,
|
756
|
-
description=f"Module exports for {tag}",
|
757
|
-
)
|
758
|
-
|
759
|
-
def _generate_main_client_file(self, ops_by_tag: dict) -> GeneratedFile:
|
760
|
-
"""Generate main client.ts with APIClient."""
|
761
|
-
tags = sorted(ops_by_tag.keys())
|
762
|
-
|
763
|
-
# Prepare tags data for template
|
764
|
-
tags_data = [
|
765
|
-
{
|
766
|
-
"class_name": self.tag_to_class_name(tag),
|
767
|
-
"slug": self.tag_and_app_to_folder_name(tag, ops_by_tag[tag]),
|
768
|
-
}
|
769
|
-
for tag in tags
|
770
|
-
]
|
771
|
-
|
772
|
-
# Generate main APIClient class
|
773
|
-
client_code = self._generate_main_client_class(ops_by_tag)
|
774
|
-
|
775
|
-
template = self.jinja_env.get_template('typescript/client/main_client_file.ts.jinja')
|
776
|
-
content = template.render(
|
777
|
-
tags=tags_data,
|
778
|
-
client_code=client_code
|
779
|
-
)
|
780
|
-
|
781
|
-
return GeneratedFile(
|
782
|
-
path="client.ts",
|
783
|
-
content=content,
|
784
|
-
description="Main API client with HTTP adapter and error handling",
|
785
|
-
)
|
786
|
-
|
787
|
-
def _generate_main_index_file(self) -> GeneratedFile:
|
788
|
-
"""Generate main index.ts with API class and JWT management."""
|
789
|
-
ops_by_tag = self.group_operations_by_tag()
|
790
|
-
tags = sorted(ops_by_tag.keys())
|
791
|
-
|
792
|
-
# Prepare tags data for template
|
793
|
-
tags_data = [
|
794
|
-
{
|
795
|
-
"class_name": self.tag_to_class_name(tag, suffix=""),
|
796
|
-
"property": self.tag_to_property_name(tag),
|
797
|
-
"slug": self.tag_and_app_to_folder_name(tag, ops_by_tag[tag]),
|
798
|
-
}
|
799
|
-
for tag in tags
|
800
|
-
]
|
801
|
-
|
802
|
-
# Check if we have enums
|
803
|
-
all_schemas = self.context.schemas
|
804
|
-
all_enums = self._collect_enums_from_schemas(all_schemas)
|
805
|
-
|
806
|
-
template = self.jinja_env.get_template('typescript/main_index.ts.jinja')
|
807
|
-
content = template.render(
|
808
|
-
api_title=self.context.openapi_info.title,
|
809
|
-
tags=tags_data,
|
810
|
-
has_enums=bool(all_enums)
|
811
|
-
)
|
812
|
-
|
813
|
-
return GeneratedFile(
|
814
|
-
path="index.ts",
|
815
|
-
content=content,
|
816
|
-
description="Main index with API class and JWT management",
|
817
|
-
)
|
818
|
-
|
819
|
-
def _generate_http_adapter_file(self) -> GeneratedFile:
|
820
|
-
"""Generate http.ts with HttpClient adapter interface."""
|
821
|
-
template = self.jinja_env.get_template('typescript/utils/http.ts.jinja')
|
822
|
-
content = template.render()
|
823
|
-
|
824
|
-
return GeneratedFile(
|
825
|
-
path="http.ts",
|
826
|
-
content=content,
|
827
|
-
description="HTTP client adapter interface and implementations",
|
828
|
-
)
|
829
|
-
|
830
|
-
def _generate_errors_file(self) -> GeneratedFile:
|
831
|
-
"""Generate errors.ts with APIError class."""
|
832
|
-
template = self.jinja_env.get_template('typescript/utils/errors.ts.jinja')
|
833
|
-
content = template.render()
|
834
|
-
|
835
|
-
return GeneratedFile(
|
836
|
-
path="errors.ts",
|
837
|
-
content=content,
|
838
|
-
description="API error classes",
|
839
|
-
)
|
840
|
-
|
841
|
-
def _generate_storage_file(self) -> GeneratedFile:
|
842
|
-
"""Generate storage.ts with StorageAdapter implementations."""
|
843
|
-
template = self.jinja_env.get_template('typescript/utils/storage.ts.jinja')
|
844
|
-
content = template.render()
|
845
|
-
|
846
|
-
return GeneratedFile(
|
847
|
-
path="storage.ts",
|
848
|
-
content=content,
|
849
|
-
description="Storage adapters for cross-platform support",
|
850
|
-
)
|
851
|
-
|
852
|
-
def _generate_logger_file(self) -> GeneratedFile:
|
853
|
-
"""Generate logger.ts with Consola integration."""
|
854
|
-
template = self.jinja_env.get_template('typescript/utils/logger.ts.jinja')
|
855
|
-
content = template.render()
|
856
|
-
|
857
|
-
return GeneratedFile(
|
858
|
-
path="logger.ts",
|
859
|
-
content=content,
|
860
|
-
description="API Logger with Consola",
|
861
|
-
)
|
862
|
-
|
863
|
-
def _generate_schema_file(self) -> GeneratedFile:
|
864
|
-
"""Generate schema.ts with OpenAPI schema as const."""
|
865
|
-
template = self.jinja_env.get_template('typescript/utils/schema.ts.jinja')
|
866
|
-
content = template.render(schema=self.openapi_schema)
|
867
|
-
|
868
|
-
return GeneratedFile(
|
869
|
-
path="schema.ts",
|
870
|
-
content=content,
|
871
|
-
description="OpenAPI Schema",
|
872
|
-
)
|