django-cfg 1.4.11__py3-none-any.whl → 1.4.14__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 (109) hide show
  1. django_cfg/apps/urls.py +120 -108
  2. django_cfg/core/generation/integration_generators/api.py +2 -1
  3. django_cfg/core/integration/url_integration.py +5 -10
  4. django_cfg/models/django/openapi.py +15 -128
  5. django_cfg/modules/django_client/core/archive/manager.py +2 -2
  6. django_cfg/modules/django_client/core/config/config.py +20 -0
  7. django_cfg/modules/django_client/core/config/service.py +1 -1
  8. django_cfg/modules/django_client/core/generator/__init__.py +4 -4
  9. django_cfg/modules/django_client/core/generator/base.py +71 -0
  10. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  11. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  12. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  13. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  14. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  15. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  16. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  17. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/api_wrapper.py.jinja +25 -2
  18. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client.py.jinja +24 -6
  19. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client_file.py.jinja +1 -0
  20. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/operation_method.py.jinja +3 -1
  21. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/sub_client.py.jinja +8 -1
  22. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  23. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  24. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  25. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/main_init.py.jinja +2 -0
  26. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enum_class.py.jinja +3 -1
  27. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/schema_class.py.jinja +3 -1
  28. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  29. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  30. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  31. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  32. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  33. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  34. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  35. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +539 -0
  36. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  37. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  38. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  39. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  40. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/app_client.ts.jinja +1 -1
  41. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/client.ts.jinja +77 -1
  42. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/main_client_file.ts.jinja +1 -0
  43. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/sub_client.ts.jinja +3 -3
  44. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  45. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  46. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/main_index.ts.jinja +73 -11
  47. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  48. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  49. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  50. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  51. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/errors.ts.jinja +3 -1
  52. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/logger.ts.jinja +9 -1
  53. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  54. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/storage.ts.jinja +54 -10
  55. django_cfg/modules/django_client/management/commands/generate_client.py +5 -0
  56. django_cfg/modules/django_client/pytest.ini +30 -0
  57. django_cfg/modules/django_client/spectacular/__init__.py +3 -2
  58. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  59. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  60. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  61. django_cfg/modules/django_unfold/callbacks/main.py +6 -6
  62. django_cfg/modules/django_unfold/dashboard.py +6 -6
  63. django_cfg/pyproject.toml +1 -1
  64. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/METADATA +1 -1
  65. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/RECORD +100 -78
  66. django_cfg/dashboard/DEBUG_README.md +0 -105
  67. django_cfg/dashboard/REFACTORING_SUMMARY.md +0 -237
  68. django_cfg/modules/django_client/core/generator/python.py +0 -751
  69. django_cfg/modules/django_client/core/generator/typescript.py +0 -872
  70. django_cfg/modules/django_drf_theme/CHANGELOG.md +0 -210
  71. django_cfg/modules/django_drf_theme/EXAMPLE.md +0 -465
  72. django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +0 -232
  73. django_cfg/modules/django_drf_theme/README.md +0 -207
  74. django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +0 -274
  75. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/__init__.py.jinja +0 -0
  76. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/app_init.py.jinja +0 -0
  77. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/app_client.py.jinja +0 -0
  78. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/flat_client.py.jinja +0 -0
  79. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client_file.py.jinja +0 -0
  80. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/app_models.py.jinja +0 -0
  81. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enums.py.jinja +0 -0
  82. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/models.py.jinja +0 -0
  83. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/logger.py.jinja +0 -0
  84. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/schema.py.jinja +0 -0
  85. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/app_index.ts.jinja +0 -0
  86. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/flat_client.ts.jinja +0 -0
  87. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/operation.ts.jinja +0 -0
  88. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client_file.ts.jinja +0 -0
  89. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/index.ts.jinja +0 -0
  90. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/app_models.ts.jinja +0 -0
  91. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/enums.ts.jinja +0 -0
  92. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/models.ts.jinja +0 -0
  93. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/http.ts.jinja +0 -0
  94. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/schema.ts.jinja +0 -0
  95. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  96. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  97. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  98. /django_cfg/{dashboard → modules/django_dashboard}/management/__init__.py +0 -0
  99. /django_cfg/{dashboard → modules/django_dashboard}/management/commands/__init__.py +0 -0
  100. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  101. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  102. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  103. /django_cfg/{dashboard → modules/django_dashboard}/sections/documentation.py +0 -0
  104. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  105. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  106. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  107. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/WHEEL +0 -0
  108. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/entry_points.txt +0 -0
  109. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,329 @@
1
+ """
2
+ Zod Schemas Generator - Generates Zod validation schemas from IR.
3
+
4
+ This generator creates Zod schemas for runtime validation:
5
+ - Object schemas (z.object)
6
+ - Enum schemas (z.nativeEnum)
7
+ - Array schemas (z.array)
8
+ - Type inference (z.infer<typeof Schema>)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from jinja2 import Environment
14
+ from ..base import GeneratedFile, BaseGenerator
15
+ from ...ir import IRContext, IRSchemaObject
16
+
17
+
18
+ class SchemasGenerator:
19
+ """
20
+ Generate Zod schemas from IR schemas.
21
+
22
+ Features:
23
+ - Runtime validation with Zod
24
+ - Type inference from schemas
25
+ - Enum validation with z.nativeEnum()
26
+ - Nested object validation
27
+ - Array and nullable types
28
+ """
29
+
30
+ def __init__(self, jinja_env: Environment, context: IRContext, base: BaseGenerator):
31
+ self.jinja_env = jinja_env
32
+ self.context = context
33
+ self.base = base
34
+
35
+ def generate_schema(self, schema: IRSchemaObject) -> str:
36
+ """
37
+ Generate Zod schema for a single IR schema.
38
+
39
+ Args:
40
+ schema: IRSchemaObject to convert to Zod
41
+
42
+ Returns:
43
+ Zod schema code as string
44
+
45
+ Examples:
46
+ >>> generate_schema(User)
47
+ export const UserSchema = z.object({
48
+ id: z.number(),
49
+ email: z.string().email(),
50
+ username: z.string().min(1).max(150),
51
+ })
52
+ """
53
+ if schema.type == "object":
54
+ return self._generate_object_schema(schema)
55
+ elif schema.type == "array":
56
+ return self._generate_array_schema(schema)
57
+ elif schema.enum:
58
+ return self._generate_enum_schema(schema)
59
+ else:
60
+ # Primitive type
61
+ return self._map_type_to_zod(schema)
62
+
63
+ def _generate_object_schema(self, schema: IRSchemaObject) -> str:
64
+ """Generate z.object() schema."""
65
+ lines = []
66
+
67
+ # Schema comment
68
+ if schema.description:
69
+ lines.append(f"/**\n * {schema.description}\n */")
70
+
71
+ # Start schema definition
72
+ lines.append(f"export const {schema.name}Schema = z.object({{")
73
+
74
+ # Generate fields
75
+ if schema.properties:
76
+ for prop_name, prop_schema in schema.properties.items():
77
+ field_code = self._generate_field(prop_name, prop_schema, schema.required)
78
+ lines.append(f" {field_code},")
79
+
80
+ lines.append("})")
81
+
82
+ return "\n".join(lines)
83
+
84
+ def _generate_field(
85
+ self,
86
+ name: str,
87
+ schema: IRSchemaObject,
88
+ required_fields: list[str],
89
+ ) -> str:
90
+ """
91
+ Generate Zod field validation.
92
+
93
+ Examples:
94
+ id: z.number()
95
+ email: z.string().email()
96
+ username: z.string().min(1).max(150)
97
+ status: z.nativeEnum(Enums.StatusEnum)
98
+ created_at: z.string().datetime()
99
+ """
100
+ # Check if this field is an enum
101
+ if schema.enum and schema.name:
102
+ # Use enum validation
103
+ zod_type = f"z.nativeEnum(Enums.{self.base.sanitize_enum_name(schema.name)})"
104
+ # Check if this field is a reference to an enum
105
+ elif schema.ref and schema.ref in self.context.schemas:
106
+ ref_schema = self.context.schemas[schema.ref]
107
+ if ref_schema.enum:
108
+ # Reference to enum component
109
+ zod_type = f"z.nativeEnum(Enums.{self.base.sanitize_enum_name(schema.ref)})"
110
+ else:
111
+ # Reference to another schema
112
+ zod_type = f"{schema.ref}Schema"
113
+ else:
114
+ # Map TypeScript type to Zod type
115
+ zod_type = self._map_type_to_zod(schema)
116
+
117
+ # Check if required
118
+ is_required = name in required_fields
119
+
120
+ # Handle optional fields
121
+ if not is_required:
122
+ zod_type = f"{zod_type}.optional()"
123
+
124
+ # Handle nullable fields
125
+ if schema.nullable:
126
+ zod_type = f"{zod_type}.nullable()"
127
+
128
+ return f"{name}: {zod_type}"
129
+
130
+ def _map_type_to_zod(self, schema: IRSchemaObject) -> str:
131
+ """
132
+ Map OpenAPI/TypeScript type to Zod validation.
133
+
134
+ Args:
135
+ schema: IRSchemaObject with type information
136
+
137
+ Returns:
138
+ Zod validation code
139
+
140
+ Examples:
141
+ string -> z.string()
142
+ string (format: email) -> z.string().email()
143
+ string (format: date-time) -> z.string().datetime()
144
+ string (format: uri) -> z.string().url()
145
+ integer -> z.number().int()
146
+ number -> z.number()
147
+ boolean -> z.boolean()
148
+ array -> z.array(...)
149
+ """
150
+ schema_type = schema.type
151
+ schema_format = schema.format
152
+
153
+ # String types with format validation
154
+ if schema_type == "string":
155
+ base_type = "z.string()"
156
+
157
+ # Add format validation
158
+ if schema_format == "email":
159
+ base_type = "z.string().email()"
160
+ elif schema_format in ("date-time", "datetime"):
161
+ base_type = "z.string().datetime()"
162
+ elif schema_format == "date":
163
+ base_type = "z.string().date()"
164
+ elif schema_format in ("uri", "url"):
165
+ base_type = "z.string().url()"
166
+ elif schema_format == "uuid":
167
+ base_type = "z.string().uuid()"
168
+
169
+ # Add length constraints
170
+ if schema.min_length is not None:
171
+ base_type = f"{base_type}.min({schema.min_length})"
172
+ if schema.max_length is not None:
173
+ base_type = f"{base_type}.max({schema.max_length})"
174
+
175
+ # Add pattern validation
176
+ if schema.pattern:
177
+ # Escape regex pattern for JS
178
+ escaped_pattern = schema.pattern.replace('\\', '\\\\')
179
+ base_type = f"{base_type}.regex(/{escaped_pattern}/)"
180
+
181
+ return base_type
182
+
183
+ # Integer type
184
+ elif schema_type == "integer":
185
+ base_type = "z.number().int()"
186
+
187
+ # Add range constraints
188
+ if schema.minimum is not None:
189
+ base_type = f"{base_type}.min({schema.minimum})"
190
+ if schema.maximum is not None:
191
+ base_type = f"{base_type}.max({schema.maximum})"
192
+
193
+ return base_type
194
+
195
+ # Number type
196
+ elif schema_type == "number":
197
+ base_type = "z.number()"
198
+
199
+ # Add range constraints
200
+ if schema.minimum is not None:
201
+ base_type = f"{base_type}.min({schema.minimum})"
202
+ if schema.maximum is not None:
203
+ base_type = f"{base_type}.max({schema.maximum})"
204
+
205
+ return base_type
206
+
207
+ # Boolean type
208
+ elif schema_type == "boolean":
209
+ return "z.boolean()"
210
+
211
+ # Array type
212
+ elif schema_type == "array":
213
+ if schema.items:
214
+ item_type = self._map_type_to_zod(schema.items)
215
+ return f"z.array({item_type})"
216
+ return "z.array(z.any())"
217
+
218
+ # Object type
219
+ elif schema_type == "object":
220
+ # Only reference schema if it's a defined component (not an inline property)
221
+ # Inline objects should use z.record() or z.object({})
222
+ if schema.ref:
223
+ # Explicit reference
224
+ return f"{schema.ref}Schema"
225
+ elif schema.properties:
226
+ # Inline object with properties - shouldn't reach here, but use z.object
227
+ return "z.object({})"
228
+ else:
229
+ # Object with no properties (like additionalProperties: {})
230
+ # Use z.record(z.string(), z.any()) for dynamic objects
231
+ return "z.record(z.string(), z.any())"
232
+
233
+ # Fallback to any
234
+ return "z.any()"
235
+
236
+ def _generate_array_schema(self, schema: IRSchemaObject) -> str:
237
+ """Generate z.array() schema."""
238
+ lines = []
239
+
240
+ if schema.description:
241
+ lines.append(f"/**\n * {schema.description}\n */")
242
+
243
+ item_type = "z.any()"
244
+ if schema.items:
245
+ item_type = self._map_type_to_zod(schema.items)
246
+
247
+ lines.append(f"export const {schema.name}Schema = z.array({item_type})")
248
+
249
+ return "\n".join(lines)
250
+
251
+ def _generate_enum_schema(self, schema: IRSchemaObject) -> str:
252
+ """Generate z.nativeEnum() schema."""
253
+ enum_name = self.base.sanitize_enum_name(schema.name)
254
+
255
+ lines = []
256
+
257
+ if schema.description:
258
+ lines.append(f"/**\n * {schema.description}\n */")
259
+
260
+ lines.append(f"export const {enum_name}Schema = z.nativeEnum(Enums.{enum_name})")
261
+
262
+ return "\n".join(lines)
263
+
264
+ def generate_schema_file(self, schema: IRSchemaObject, refs: set[str]) -> GeneratedFile:
265
+ """
266
+ Generate individual Zod schema file.
267
+
268
+ Args:
269
+ schema: Schema to generate
270
+ refs: Set of schema names that are referenced
271
+
272
+ Returns:
273
+ GeneratedFile with Zod schema
274
+ """
275
+ # Generate schema code
276
+ schema_code = self.generate_schema(schema)
277
+
278
+ # Check if has enums
279
+ has_enums = self._schema_uses_enums(schema)
280
+
281
+ # Render template
282
+ template = self.jinja_env.get_template("schemas/schema.ts.jinja")
283
+ content = template.render(
284
+ schema_name=schema.name,
285
+ description=schema.description,
286
+ schema_code=schema_code,
287
+ has_enums=has_enums,
288
+ has_refs=bool(refs),
289
+ refs=sorted(refs),
290
+ )
291
+
292
+ return GeneratedFile(
293
+ path=f"_utils/schemas/{schema.name}.schema.ts",
294
+ content=content,
295
+ description=f"Zod schema for {schema.name}",
296
+ )
297
+
298
+ def generate_schemas_index_file(self, schema_names: list[str]) -> GeneratedFile:
299
+ """Generate index.ts for schemas folder."""
300
+ template = self.jinja_env.get_template("schemas/index.ts.jinja")
301
+ content = template.render(schema_names=sorted(schema_names))
302
+
303
+ return GeneratedFile(
304
+ path="_utils/schemas/index.ts",
305
+ content=content,
306
+ description="Zod schemas index",
307
+ )
308
+
309
+ def _schema_uses_enums(self, schema: IRSchemaObject) -> bool:
310
+ """Check if schema uses any enums."""
311
+ if schema.enum:
312
+ return True
313
+
314
+ if schema.properties:
315
+ for prop in schema.properties.values():
316
+ if prop.enum or (prop.ref and self._is_enum_ref(prop.ref)):
317
+ return True
318
+
319
+ if schema.items:
320
+ if schema.items.enum or (schema.items.ref and self._is_enum_ref(schema.items.ref)):
321
+ return True
322
+
323
+ return False
324
+
325
+ def _is_enum_ref(self, ref: str) -> bool:
326
+ """Check if reference points to an enum."""
327
+ if ref in self.context.schemas:
328
+ return self.context.schemas[ref].enum is not None
329
+ return False
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Global API Instance - Singleton configuration
3
+ *
4
+ * This module provides a global API instance that can be configured once
5
+ * and used throughout your application.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * // Configure once (e.g., in your app entry point)
10
+ * import { configureAPI } from './api-instance'
11
+ *
12
+ * configureAPI({
13
+ * baseUrl: 'https://api.example.com',
14
+ * token: 'your-jwt-token'
15
+ * })
16
+ *
17
+ * // Then use fetchers and hooks anywhere without configuration
18
+ * import { getUsers } from './fetchers'
19
+ * const users = await getUsers({ page: 1 })
20
+ * ```
21
+ *
22
+ * For SSR or multiple instances:
23
+ * ```typescript
24
+ * import { API } from './index'
25
+ * import { getUsers } from './fetchers'
26
+ *
27
+ * const api = new API('https://api.example.com')
28
+ * const users = await getUsers({ page: 1 }, api)
29
+ * ```
30
+ */
31
+
32
+ import { API, type APIOptions } from './index'
33
+
34
+ let globalAPI: API | null = null
35
+
36
+ /**
37
+ * Get the global API instance
38
+ * @throws Error if API is not configured
39
+ */
40
+ export function getAPIInstance(): API {
41
+ if (!globalAPI) {
42
+ throw new Error(
43
+ 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\n' +
44
+ 'Example:\n' +
45
+ ' import { configureAPI } from "./api-instance"\n' +
46
+ ' configureAPI({ baseUrl: "https://api.example.com" })'
47
+ )
48
+ }
49
+ return globalAPI
50
+ }
51
+
52
+ /**
53
+ * Check if API is configured
54
+ */
55
+ export function isAPIConfigured(): boolean {
56
+ return globalAPI !== null
57
+ }
58
+
59
+ /**
60
+ * Configure the global API instance
61
+ *
62
+ * @param baseUrl - Base URL for the API
63
+ * @param options - Optional configuration (storage, retry, logger)
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * configureAPI({
68
+ * baseUrl: 'https://api.example.com',
69
+ * token: 'jwt-token',
70
+ * options: {
71
+ * retryConfig: { maxRetries: 3 },
72
+ * loggerConfig: { enabled: true }
73
+ * }
74
+ * })
75
+ * ```
76
+ */
77
+ export function configureAPI(config: {
78
+ baseUrl: string
79
+ token?: string
80
+ refreshToken?: string
81
+ options?: APIOptions
82
+ }): API {
83
+ globalAPI = new API(config.baseUrl, config.options)
84
+
85
+ if (config.token) {
86
+ globalAPI.setToken(config.token, config.refreshToken)
87
+ }
88
+
89
+ return globalAPI
90
+ }
91
+
92
+ /**
93
+ * Reconfigure the global API instance with new settings
94
+ * Useful for updating tokens or base URL
95
+ */
96
+ export function reconfigureAPI(updates: {
97
+ baseUrl?: string
98
+ token?: string
99
+ refreshToken?: string
100
+ }): API {
101
+ const instance = getAPIInstance()
102
+
103
+ if (updates.baseUrl) {
104
+ instance.setBaseUrl(updates.baseUrl)
105
+ }
106
+
107
+ if (updates.token) {
108
+ instance.setToken(updates.token, updates.refreshToken)
109
+ }
110
+
111
+ return instance
112
+ }
113
+
114
+ /**
115
+ * Clear tokens from the global API instance
116
+ */
117
+ export function clearAPITokens(): void {
118
+ const instance = getAPIInstance()
119
+ instance.clearTokens()
120
+ }
121
+
122
+ /**
123
+ * Reset the global API instance
124
+ * Useful for testing or logout scenarios
125
+ */
126
+ export function resetAPI(): void {
127
+ if (globalAPI) {
128
+ globalAPI.clearTokens()
129
+ }
130
+ globalAPI = null
131
+ }
@@ -12,7 +12,7 @@ export class {{ class_name }} {
12
12
  }
13
13
 
14
14
  {% for operation in operations %}
15
- {{ operation }}
15
+ {{ operation }}
16
16
 
17
17
  {% endfor %}
18
18
  }
@@ -7,6 +7,7 @@ import { {{ tag.class_name }} } from "./{{ tag.slug }}";
7
7
  import { HttpClientAdapter, FetchAdapter } from "./http";
8
8
  import { APIError, NetworkError } from "./errors";
9
9
  import { APILogger, type LoggerConfig } from "./logger";
10
+ import { withRetry, type RetryConfig } from "./retry";
10
11
 
11
12
 
12
13
  {% endif %}
@@ -29,6 +30,7 @@ export class APIClient {
29
30
  private baseUrl: string;
30
31
  private httpClient: HttpClientAdapter;
31
32
  private logger: APILogger | null = null;
33
+ private retryConfig: RetryConfig | null = null;
32
34
 
33
35
  // Sub-clients
34
36
  {% for tag in tags %}
@@ -40,6 +42,7 @@ export class APIClient {
40
42
  options?: {
41
43
  httpClient?: HttpClientAdapter;
42
44
  loggerConfig?: Partial<LoggerConfig>;
45
+ retryConfig?: RetryConfig;
43
46
  }
44
47
  ) {
45
48
  this.baseUrl = baseUrl.replace(/\/$/, '');
@@ -50,6 +53,11 @@ export class APIClient {
50
53
  this.logger = new APILogger(options.loggerConfig);
51
54
  }
52
55
 
56
+ // Store retry configuration
57
+ if (options?.retryConfig !== undefined) {
58
+ this.retryConfig = options.retryConfig;
59
+ }
60
+
53
61
  // Initialize sub-clients
54
62
  {% for tag in tags %}
55
63
  this.{{ tag.property }} = new {{ tag.class_name }}(this);
@@ -71,6 +79,7 @@ export class APIClient {
71
79
 
72
80
  /**
73
81
  * Make HTTP request with Django CSRF and session handling.
82
+ * Automatically retries on network errors and 5xx server errors.
74
83
  */
75
84
  async request<T>(
76
85
  method: string,
@@ -80,6 +89,41 @@ export class APIClient {
80
89
  body?: any;
81
90
  formData?: FormData;
82
91
  }
92
+ ): Promise<T> {
93
+ // Wrap request in retry logic if configured
94
+ if (this.retryConfig) {
95
+ return withRetry(() => this._makeRequest<T>(method, path, options), {
96
+ ...this.retryConfig,
97
+ onFailedAttempt: (info) => {
98
+ // Log retry attempts
99
+ if (this.logger) {
100
+ this.logger.warn(
101
+ `Retry attempt ${info.attemptNumber}/${info.retriesLeft + info.attemptNumber} ` +
102
+ `for ${method} ${path}: ${info.error.message}`
103
+ );
104
+ }
105
+ // Call user's onFailedAttempt if provided
106
+ this.retryConfig?.onFailedAttempt?.(info);
107
+ },
108
+ });
109
+ }
110
+
111
+ // No retry configured, make request directly
112
+ return this._makeRequest<T>(method, path, options);
113
+ }
114
+
115
+ /**
116
+ * Internal request method (without retry wrapper).
117
+ * Used by request() method with optional retry logic.
118
+ */
119
+ private async _makeRequest<T>(
120
+ method: string,
121
+ path: string,
122
+ options?: {
123
+ params?: Record<string, any>;
124
+ body?: any;
125
+ formData?: FormData;
126
+ }
83
127
  ): Promise<T> {
84
128
  const url = new URL(path, this.baseUrl);
85
129
  const startTime = Date.now();
@@ -216,6 +260,7 @@ export class APIClient {
216
260
  import * as Enums from "./enums";
217
261
  import { HttpClientAdapter, FetchAdapter } from "./http";
218
262
  import { APIError, NetworkError } from "./errors";
263
+ import { withRetry, type RetryConfig } from "./retry";
219
264
 
220
265
 
221
266
  /**
@@ -235,13 +280,22 @@ import { APIError, NetworkError } from "./errors";
235
280
  export class APIClient {
236
281
  private baseUrl: string;
237
282
  private httpClient: HttpClientAdapter;
283
+ private retryConfig: RetryConfig | null = null;
238
284
 
239
285
  constructor(
240
286
  baseUrl: string,
241
- options?: { httpClient?: HttpClientAdapter }
287
+ options?: {
288
+ httpClient?: HttpClientAdapter;
289
+ retryConfig?: RetryConfig;
290
+ }
242
291
  ) {
243
292
  this.baseUrl = baseUrl.replace(/\/$/, '');
244
293
  this.httpClient = options?.httpClient || new FetchAdapter();
294
+
295
+ // Store retry configuration
296
+ if (options?.retryConfig !== undefined) {
297
+ this.retryConfig = options.retryConfig;
298
+ }
245
299
  }
246
300
 
247
301
  /**
@@ -259,6 +313,7 @@ export class APIClient {
259
313
 
260
314
  /**
261
315
  * Make HTTP request with Django CSRF and session handling.
316
+ * Automatically retries on network errors and 5xx server errors.
262
317
  */
263
318
  private async request<T>(
264
319
  method: string,
@@ -268,6 +323,27 @@ export class APIClient {
268
323
  body?: any;
269
324
  formData?: FormData;
270
325
  }
326
+ ): Promise<T> {
327
+ // Wrap request in retry logic if configured
328
+ if (this.retryConfig) {
329
+ return withRetry(() => this._makeRequest<T>(method, path, options), this.retryConfig);
330
+ }
331
+
332
+ // No retry configured, make request directly
333
+ return this._makeRequest<T>(method, path, options);
334
+ }
335
+
336
+ /**
337
+ * Internal request method (without retry wrapper).
338
+ */
339
+ private async _makeRequest<T>(
340
+ method: string,
341
+ path: string,
342
+ options?: {
343
+ params?: Record<string, any>;
344
+ body?: any;
345
+ formData?: FormData;
346
+ }
271
347
  ): Promise<T> {
272
348
  const url = new URL(path, this.baseUrl);
273
349
 
@@ -4,6 +4,7 @@ import { {{ tag.class_name }} } from "./{{ tag.slug }}";
4
4
  import { HttpClientAdapter, FetchAdapter } from "./http";
5
5
  import { APIError, NetworkError } from "./errors";
6
6
  import { APILogger, type LoggerConfig } from "./logger";
7
+ import { withRetry, type RetryConfig } from "./retry";
7
8
 
8
9
 
9
10
  {{ client_code }}
@@ -2,14 +2,14 @@
2
2
  * API endpoints for {{ tag }}.
3
3
  */
4
4
  class {{ class_name }} {
5
- private client: APIClient;
5
+ private client: any;
6
6
 
7
- constructor(client: APIClient) {
7
+ constructor(client: any) {
8
8
  this.client = client;
9
9
  }
10
10
 
11
11
  {% for operation in operations %}
12
- {{ operation }}
12
+ {{ operation }}
13
13
 
14
14
  {% endfor %}
15
15
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Typed fetchers for {{ tag_display_name }}
3
+ *
4
+ * Universal functions that work in any environment:
5
+ * - Next.js (App Router / Pages Router / Server Components)
6
+ * - React Native
7
+ * - Node.js backend
8
+ *
9
+ * These fetchers use Zod schemas for runtime validation.
10
+ *
11
+ * Usage:
12
+ * ```typescript
13
+ * // Configure API once (in your app entry point)
14
+ * import { configureAPI } from '../../api-instance'
15
+ * configureAPI({ baseUrl: 'https://api.example.com' })
16
+ *
17
+ * // Then use fetchers anywhere
18
+ * const users = await getUsers({ page: 1 })
19
+ *
20
+ * // With SWR
21
+ * const { data } = useSWR(['users', params], () => getUsers(params))
22
+ *
23
+ * // With React Query
24
+ * const { data } = useQuery(['users', params], () => getUsers(params))
25
+ *
26
+ * // In Server Component or SSR (pass custom client)
27
+ * import { API } from '../../index'
28
+ * const api = new API('https://api.example.com')
29
+ * const users = await getUsers({ page: 1 }, api)
30
+ * ```
31
+ */
32
+ {% if has_schemas %}
33
+ {% for schema_name in schema_names %}
34
+ import { {{ schema_name }}Schema, type {{ schema_name }} } from '../schemas/{{ schema_name }}.schema'
35
+ {% endfor %}
36
+ {% endif %}
37
+ {% if has_client %}
38
+ import { getAPIInstance } from '../../api-instance'
39
+ import type { API } from '../../index'
40
+ {% endif %}
41
+
42
+ {% for fetcher in fetchers %}
43
+ {{ fetcher }}
44
+
45
+ {% endfor %}