django-cfg 1.4.10__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 (225) hide show
  1. django_cfg/apps/agents/management/commands/create_agent.py +1 -1
  2. django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
  3. django_cfg/apps/newsletter/serializers.py +40 -3
  4. django_cfg/apps/newsletter/views/campaigns.py +12 -3
  5. django_cfg/apps/newsletter/views/emails.py +14 -3
  6. django_cfg/apps/newsletter/views/subscriptions.py +12 -2
  7. django_cfg/apps/payments/views/api/currencies.py +49 -6
  8. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  9. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  10. django_cfg/apps/payments/views/overview/views.py +2 -1
  11. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  12. django_cfg/apps/urls.py +106 -45
  13. django_cfg/core/base/config_model.py +2 -2
  14. django_cfg/core/constants.py +1 -1
  15. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  16. django_cfg/core/generation/integration_generators/api.py +73 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/management/commands/check_endpoints.py +11 -160
  20. django_cfg/management/commands/check_settings.py +13 -348
  21. django_cfg/management/commands/clear_constance.py +13 -201
  22. django_cfg/management/commands/create_token.py +13 -321
  23. django_cfg/management/commands/generate_clients.py +23 -0
  24. django_cfg/management/commands/list_urls.py +13 -306
  25. django_cfg/management/commands/migrate_all.py +13 -126
  26. django_cfg/management/commands/migrator.py +13 -396
  27. django_cfg/management/commands/rundramatiq.py +15 -247
  28. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  29. django_cfg/management/commands/runserver_ngrok.py +15 -160
  30. django_cfg/management/commands/script.py +12 -488
  31. django_cfg/management/commands/show_config.py +12 -215
  32. django_cfg/management/commands/show_urls.py +12 -342
  33. django_cfg/management/commands/superuser.py +15 -295
  34. django_cfg/management/commands/task_clear.py +14 -217
  35. django_cfg/management/commands/task_status.py +13 -248
  36. django_cfg/management/commands/test_email.py +15 -86
  37. django_cfg/management/commands/test_telegram.py +14 -61
  38. django_cfg/management/commands/test_twilio.py +15 -105
  39. django_cfg/management/commands/tree.py +13 -383
  40. django_cfg/management/commands/validate_openapi.py +10 -0
  41. django_cfg/middleware/README.md +1 -1
  42. django_cfg/middleware/user_activity.py +3 -3
  43. django_cfg/models/__init__.py +2 -2
  44. django_cfg/models/api/drf/spectacular.py +6 -6
  45. django_cfg/models/django/__init__.py +2 -2
  46. django_cfg/models/django/openapi.py +162 -0
  47. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  48. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  49. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  50. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  51. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  52. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  53. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  54. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  55. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  56. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  57. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  58. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  59. django_cfg/modules/django_client/__init__.py +20 -0
  60. django_cfg/modules/django_client/apps.py +35 -0
  61. django_cfg/modules/django_client/core/__init__.py +56 -0
  62. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  63. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  64. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  65. django_cfg/modules/django_client/core/cli/main.py +235 -0
  66. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  67. django_cfg/modules/django_client/core/config/config.py +208 -0
  68. django_cfg/modules/django_client/core/config/group.py +101 -0
  69. django_cfg/modules/django_client/core/config/service.py +209 -0
  70. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  71. django_cfg/modules/django_client/core/generator/base.py +838 -0
  72. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  73. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  74. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  75. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  76. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  77. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  78. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  79. django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
  80. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
  81. django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
  82. django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
  83. django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
  84. django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
  85. django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
  86. django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
  87. django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
  88. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  89. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  90. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  91. django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
  92. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
  93. django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
  94. django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
  95. django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
  96. django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
  97. django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
  98. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  99. django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
  100. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  101. django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
  102. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  103. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  104. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  105. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  106. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  107. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
  108. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  109. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  110. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  111. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  112. django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
  113. django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
  114. django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
  115. django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
  116. django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
  117. django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
  118. django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
  119. django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
  120. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  121. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  122. django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
  123. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
  124. django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
  125. django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
  126. django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
  127. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  128. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  129. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  130. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  131. django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
  132. django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
  133. django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
  134. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  135. django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
  136. django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
  137. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  138. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  139. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  140. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  141. django_cfg/modules/django_client/core/ir/context.py +387 -0
  142. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  143. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  144. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  145. django_cfg/modules/django_client/core/parser/base.py +648 -0
  146. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  147. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  148. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  149. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  150. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  151. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  152. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  153. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  154. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  155. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  156. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  157. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  158. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  159. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  160. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  161. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  162. django_cfg/modules/django_client/management/__init__.py +3 -0
  163. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  164. django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
  165. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  166. django_cfg/modules/django_client/pytest.ini +30 -0
  167. django_cfg/modules/django_client/spectacular/__init__.py +10 -0
  168. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  169. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  170. django_cfg/modules/django_client/urls.py +72 -0
  171. django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
  172. django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
  173. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  174. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  175. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  176. django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
  177. django_cfg/modules/django_email/management/__init__.py +0 -0
  178. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  179. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  180. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  181. django_cfg/modules/django_logging/django_logger.py +6 -6
  182. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  183. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  184. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  185. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  186. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  187. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  188. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  189. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  190. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  191. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  192. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  193. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  194. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  195. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  196. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  197. django_cfg/modules/django_unfold/callbacks/main.py +21 -10
  198. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  199. django_cfg/pyproject.toml +2 -6
  200. django_cfg/registry/third_party.py +5 -7
  201. django_cfg/routing/callbacks.py +1 -1
  202. django_cfg/static/admin/css/prose-unfold.css +666 -0
  203. django_cfg/templates/admin/index.html +8 -0
  204. django_cfg/templates/admin/index_new.html +13 -0
  205. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  206. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  207. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  208. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
  209. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
  210. django_cfg/management/commands/generate.py +0 -107
  211. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  212. /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
  213. /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
  214. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  215. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  216. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  217. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  218. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  219. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  220. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  221. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  222. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  223. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
  224. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
  225. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,207 @@
1
+ """
2
+ TypeScript Files Generator - Generates utility files (index, http, errors, logger, etc.).
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from jinja2 import Environment
8
+ from ...ir import IROperationObject
9
+ from ..base import GeneratedFile
10
+
11
+
12
+ class FilesGenerator:
13
+ """Generates TypeScript utility files."""
14
+
15
+ def __init__(self, jinja_env: Environment, context, base):
16
+ self.jinja_env = jinja_env
17
+ self.context = context
18
+ self.base = base
19
+ self.openapi_schema = getattr(base, 'openapi_schema', None)
20
+
21
+ def generate_index_file(self):
22
+ """Generate index.ts with exports."""
23
+
24
+ template = self.jinja_env.get_template('index.ts.jinja')
25
+ content = template.render(
26
+ has_enums=bool(self.base.get_enum_schemas())
27
+ )
28
+
29
+ return GeneratedFile(
30
+ path="index.ts",
31
+ content=content,
32
+ description="Module exports",
33
+ )
34
+
35
+ def generate_app_index_file(self, tag: str, operations: list[IROperationObject]):
36
+ """Generate index.ts for a specific app."""
37
+ from ..base import GeneratedFile
38
+
39
+ template = self.jinja_env.get_template('app_index.ts.jinja')
40
+ content = template.render()
41
+
42
+ folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
43
+ return GeneratedFile(
44
+ path=f"{folder_name}/index.ts",
45
+ content=content,
46
+ description=f"Module exports for {tag}",
47
+ )
48
+
49
+ def generate_main_index_file(self):
50
+ """Generate main index.ts with API class and JWT management."""
51
+
52
+ ops_by_tag = self.base.group_operations_by_tag()
53
+ tags = sorted(ops_by_tag.keys())
54
+
55
+ # Prepare tags data for template
56
+ tags_data = [
57
+ {
58
+ "class_name": self.base.tag_to_class_name(tag, suffix=""),
59
+ "property": self.base.tag_to_property_name(tag),
60
+ "slug": self.base.tag_and_app_to_folder_name(tag, ops_by_tag[tag]),
61
+ }
62
+ for tag in tags
63
+ ]
64
+
65
+ # Check if we have enums
66
+ all_schemas = self.context.schemas
67
+ all_enums = self.base._collect_enums_from_schemas(all_schemas)
68
+
69
+ template = self.jinja_env.get_template('main_index.ts.jinja')
70
+ content = template.render(
71
+ api_title=self.context.openapi_info.title,
72
+ tags=tags_data,
73
+ has_enums=bool(all_enums),
74
+ generate_zod_schemas=getattr(self.base, 'generate_zod_schemas', False),
75
+ generate_fetchers=getattr(self.base, 'generate_fetchers', False),
76
+ generate_swr_hooks=getattr(self.base, 'generate_swr_hooks', False),
77
+ )
78
+
79
+ return GeneratedFile(
80
+ path="index.ts",
81
+ content=content,
82
+ description="Main index with API class and JWT management",
83
+ )
84
+
85
+ def generate_http_adapter_file(self):
86
+ """Generate http.ts with HttpClient adapter interface."""
87
+
88
+ template = self.jinja_env.get_template('utils/http.ts.jinja')
89
+ content = template.render()
90
+
91
+ return GeneratedFile(
92
+ path="http.ts",
93
+ content=content,
94
+ description="HTTP client adapter interface and implementations",
95
+ )
96
+
97
+ def generate_errors_file(self):
98
+ """Generate errors.ts with APIError class."""
99
+
100
+ template = self.jinja_env.get_template('utils/errors.ts.jinja')
101
+ content = template.render()
102
+
103
+ return GeneratedFile(
104
+ path="errors.ts",
105
+ content=content,
106
+ description="API error classes",
107
+ )
108
+
109
+ def generate_storage_file(self):
110
+ """Generate storage.ts with StorageAdapter implementations."""
111
+
112
+ template = self.jinja_env.get_template('utils/storage.ts.jinja')
113
+ content = template.render()
114
+
115
+ return GeneratedFile(
116
+ path="storage.ts",
117
+ content=content,
118
+ description="Storage adapters for cross-platform support",
119
+ )
120
+
121
+ def generate_logger_file(self):
122
+ """Generate logger.ts with Consola integration."""
123
+
124
+ template = self.jinja_env.get_template('utils/logger.ts.jinja')
125
+ content = template.render()
126
+
127
+ return GeneratedFile(
128
+ path="logger.ts",
129
+ content=content,
130
+ description="API Logger with Consola",
131
+ )
132
+
133
+ def generate_retry_file(self):
134
+ """Generate retry.ts with p-retry integration."""
135
+
136
+ template = self.jinja_env.get_template('utils/retry.ts.jinja')
137
+ content = template.render()
138
+
139
+ return GeneratedFile(
140
+ path="retry.ts",
141
+ content=content,
142
+ description="Retry utilities with p-retry",
143
+ )
144
+
145
+ def generate_api_instance_file(self):
146
+ """Generate api-instance.ts with global singleton."""
147
+
148
+ template = self.jinja_env.get_template('api_instance.ts.jinja')
149
+ content = template.render()
150
+
151
+ return GeneratedFile(
152
+ path="api-instance.ts",
153
+ content=content,
154
+ description="Global API singleton for universal configuration",
155
+ )
156
+
157
+ def generate_package_json_file(self, package_config: dict = None):
158
+ """Generate package.json for npm publishing."""
159
+ if package_config is None:
160
+ package_config = {}
161
+
162
+ # Default configuration
163
+ defaults = {
164
+ "package_name": package_config.get("name", "api-client"),
165
+ "version": package_config.get("version", "1.0.0"),
166
+ "description": package_config.get("description") or f"Auto-generated TypeScript client for {self.context.openapi_info.title}",
167
+ "author": package_config.get("author"),
168
+ "license": package_config.get("license", "MIT"),
169
+ "repository_url": package_config.get("repository_url"),
170
+ "keywords": package_config.get("keywords", ["api", "client", "typescript", "openapi"]),
171
+ "private": package_config.get("private", False),
172
+ }
173
+
174
+ # Add Zod flag
175
+ defaults["generate_zod_schemas"] = self.base.generate_zod_schemas
176
+
177
+ template = self.jinja_env.get_template('package.json.jinja')
178
+ content = template.render(**defaults)
179
+
180
+ return GeneratedFile(
181
+ path="package.json",
182
+ content=content,
183
+ description="NPM package configuration",
184
+ )
185
+
186
+ def generate_tsconfig_file(self):
187
+ """Generate tsconfig.json for TypeScript compilation."""
188
+ template = self.jinja_env.get_template('tsconfig.json.jinja')
189
+ content = template.render()
190
+
191
+ return GeneratedFile(
192
+ path="tsconfig.json",
193
+ content=content,
194
+ description="TypeScript compiler configuration",
195
+ )
196
+
197
+ def generate_schema_file(self):
198
+ """Generate schema.ts with OpenAPI schema as const."""
199
+
200
+ template = self.jinja_env.get_template('utils/schema.ts.jinja')
201
+ content = template.render(schema=self.openapi_schema)
202
+
203
+ return GeneratedFile(
204
+ path="schema.ts",
205
+ content=content,
206
+ description="OpenAPI Schema",
207
+ )
@@ -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