django-cfg 1.4.15__py3-none-any.whl → 1.4.19__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 (29) hide show
  1. django_cfg/apps/leads/urls.py +2 -1
  2. django_cfg/apps/payments/urls.py +4 -4
  3. django_cfg/core/base/config_model.py +7 -7
  4. django_cfg/core/generation/core_generators/settings.py +2 -3
  5. django_cfg/core/generation/core_generators/static.py +9 -9
  6. django_cfg/core/generation/integration_generators/api.py +1 -1
  7. django_cfg/models/infrastructure/security.py +33 -2
  8. django_cfg/modules/django_client/core/generator/base.py +18 -20
  9. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +56 -113
  10. django_cfg/modules/django_client/core/generator/typescript/generator.py +1 -1
  11. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +102 -232
  12. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +9 -4
  13. django_cfg/modules/django_client/core/generator/typescript/naming.py +83 -0
  14. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +19 -7
  15. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +40 -33
  16. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +25 -0
  17. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +31 -0
  18. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/index.ts.jinja +29 -0
  19. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/mutation_hook.ts.jinja +25 -0
  20. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/query_hook.ts.jinja +20 -0
  21. django_cfg/modules/django_client/core/groups/manager.py +51 -26
  22. django_cfg/modules/django_client/spectacular/__init__.py +3 -2
  23. django_cfg/modules/django_client/spectacular/schema.py +50 -0
  24. django_cfg/pyproject.toml +1 -1
  25. {django_cfg-1.4.15.dist-info → django_cfg-1.4.19.dist-info}/METADATA +1 -1
  26. {django_cfg-1.4.15.dist-info → django_cfg-1.4.19.dist-info}/RECORD +29 -22
  27. {django_cfg-1.4.15.dist-info → django_cfg-1.4.19.dist-info}/WHEEL +0 -0
  28. {django_cfg-1.4.15.dist-info → django_cfg-1.4.19.dist-info}/entry_points.txt +0 -0
  29. {django_cfg-1.4.15.dist-info → django_cfg-1.4.19.dist-info}/licenses/LICENSE +0 -0
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
  from jinja2 import Environment
20
20
  from ..base import BaseGenerator, GeneratedFile
21
21
  from ...ir import IROperationObject, IRContext
22
-
22
+ from .naming import operation_to_method_name
23
23
 
24
24
  class HooksGenerator:
25
25
  """
@@ -39,7 +39,7 @@ class HooksGenerator:
39
39
 
40
40
  def generate_query_hook(self, operation: IROperationObject) -> str:
41
41
  """
42
- Generate useSWR hook for GET operation.
42
+ Generate useSWR hook for GET operation using Jinja2 template.
43
43
 
44
44
  Examples:
45
45
  >>> generate_query_hook(users_list)
@@ -65,44 +65,21 @@ class HooksGenerator:
65
65
  # Get SWR key
66
66
  swr_key = self._generate_swr_key(operation)
67
67
 
68
- # Build hook
69
- lines = []
70
-
71
- # JSDoc
72
- lines.append("/**")
73
- if operation.summary:
74
- lines.append(f" * {operation.summary}")
75
- lines.append(" *")
76
- lines.append(f" * @method {operation.http_method}")
77
- lines.append(f" * @path {operation.path}")
78
- lines.append(" */")
79
-
80
- # Hook signature
81
- if param_info['func_params']:
82
- lines.append(f"export function {hook_name}({param_info['func_params']}) {{")
83
- else:
84
- lines.append(f"export function {hook_name}() {{")
85
-
86
- # useSWR call
87
- fetcher_params = param_info['fetcher_params']
88
- if fetcher_params:
89
- lines.append(f" return useSWR<{response_type}>(")
90
- lines.append(f" {swr_key},")
91
- lines.append(f" () => Fetchers.{fetcher_name}({fetcher_params})")
92
- lines.append(" )")
93
- else:
94
- lines.append(f" return useSWR<{response_type}>(")
95
- lines.append(f" {swr_key},")
96
- lines.append(f" () => Fetchers.{fetcher_name}()")
97
- lines.append(" )")
98
-
99
- lines.append("}")
100
-
101
- return "\n".join(lines)
68
+ # Render template
69
+ template = self.jinja_env.get_template('hooks/query_hook.ts.jinja')
70
+ return template.render(
71
+ operation=operation,
72
+ hook_name=hook_name,
73
+ fetcher_name=fetcher_name,
74
+ func_params=param_info['func_params'],
75
+ fetcher_params=param_info['fetcher_params'],
76
+ response_type=response_type,
77
+ swr_key=swr_key
78
+ )
102
79
 
103
80
  def generate_mutation_hook(self, operation: IROperationObject) -> str:
104
81
  """
105
- Generate mutation hook for POST/PUT/PATCH/DELETE.
82
+ Generate mutation hook for POST/PUT/PATCH/DELETE using Jinja2 template.
106
83
 
107
84
  Examples:
108
85
  >>> generate_mutation_hook(users_create)
@@ -131,114 +108,84 @@ class HooksGenerator:
131
108
  # Get revalidation keys
132
109
  revalidation_keys = self._get_revalidation_keys(operation)
133
110
 
134
- # Build hook
135
- lines = []
136
-
137
- # JSDoc
138
- lines.append("/**")
139
- if operation.summary:
140
- lines.append(f" * {operation.summary}")
141
- lines.append(" *")
142
- lines.append(f" * @method {operation.http_method}")
143
- lines.append(f" * @path {operation.path}")
144
- lines.append(" */")
145
-
146
- # Hook signature
147
- lines.append(f"export function {hook_name}() {{")
148
- lines.append(" const { mutate } = useSWRConfig()")
149
- lines.append("")
150
-
151
- # Return async function
152
- if param_info['func_params']:
153
- lines.append(f" return async ({param_info['func_params']}): Promise<{response_type}> => {{")
154
- else:
155
- lines.append(f" return async (): Promise<{response_type}> => {{")
156
-
157
- # Call fetcher
158
- fetcher_params = param_info['fetcher_params']
159
- if fetcher_params:
160
- lines.append(f" const result = await Fetchers.{fetcher_name}({fetcher_params})")
161
- else:
162
- lines.append(f" const result = await Fetchers.{fetcher_name}()")
163
-
164
- # Revalidate
165
- if revalidation_keys:
166
- lines.append("")
167
- lines.append(" // Revalidate related queries")
168
- for key in revalidation_keys:
169
- lines.append(f" mutate('{key}')")
170
-
171
- lines.append("")
172
- lines.append(" return result")
173
- lines.append(" }")
174
- lines.append("}")
175
-
176
- return "\n".join(lines)
111
+ # Render template
112
+ template = self.jinja_env.get_template('hooks/mutation_hook.ts.jinja')
113
+ return template.render(
114
+ operation=operation,
115
+ hook_name=hook_name,
116
+ fetcher_name=fetcher_name,
117
+ func_params=param_info['func_params'],
118
+ fetcher_params=param_info['fetcher_params'],
119
+ response_type=response_type,
120
+ revalidation_keys=revalidation_keys
121
+ )
177
122
 
178
123
  def _operation_to_hook_name(self, operation: IROperationObject) -> str:
179
124
  """
180
125
  Convert operation to hook name.
181
-
126
+
127
+ Hooks are organized into tag-specific files but also exported globally,
128
+ so we include the tag in the name to avoid collisions.
129
+
182
130
  Examples:
183
- users_list (GET) -> useUsersList
184
- users_retrieve (GET) -> useUsersById
185
- users_create (POST) -> useCreateUsers
186
- users_update (PUT) -> useUpdateUsers
187
- users_partial_update (PATCH) -> usePartialUpdateUsers
188
- users_destroy (DELETE) -> useDeleteUsers
131
+ cfg_support_tickets_list -> useSupportTicketsList
132
+ cfg_health_drf_retrieve -> useHealthDrf
133
+ cfg_accounts_otp_request_create -> useCreateAccountsOtpRequest
189
134
  """
190
- op_id = operation.operation_id
191
-
192
- # Keep full resource name and add suffixes for uniqueness
193
- if op_id.endswith("_list"):
194
- resource = op_id.removesuffix("_list")
195
- return f"use{self._to_pascal_case(resource)}List"
196
- elif op_id.endswith("_retrieve"):
197
- resource = op_id.removesuffix("_retrieve")
198
- # Add ById suffix to distinguish from list
199
- return f"use{self._to_pascal_case(resource)}ById"
200
- elif op_id.endswith("_create"):
201
- resource = op_id.removesuffix("_create")
202
- return f"useCreate{self._to_pascal_case(resource)}"
203
- elif op_id.endswith("_partial_update"):
204
- resource = op_id.removesuffix("_partial_update")
205
- return f"usePartialUpdate{self._to_pascal_case(resource)}"
206
- elif op_id.endswith("_update"):
207
- resource = op_id.removesuffix("_update")
208
- return f"useUpdate{self._to_pascal_case(resource)}"
209
- elif op_id.endswith("_destroy"):
210
- resource = op_id.removesuffix("_destroy")
211
- return f"useDelete{self._to_pascal_case(resource)}"
135
+
136
+ # Remove cfg_ prefix but keep tag + resource for uniqueness (same as fetchers)
137
+ operation_id = operation.operation_id
138
+ if operation_id.startswith('django_cfg_'):
139
+ operation_id = operation_id.replace('django_cfg_', '', 1)
140
+ elif operation_id.startswith('cfg_'):
141
+ operation_id = operation_id.replace('cfg_', '', 1)
142
+
143
+ # Determine prefix based on HTTP method
144
+ if operation.http_method == 'GET':
145
+ prefix = 'use'
146
+ elif operation.http_method == 'POST':
147
+ prefix = 'useCreate'
148
+ elif operation.http_method in ('PUT', 'PATCH'):
149
+ if '_partial_update' in operation_id:
150
+ prefix = 'usePartialUpdate'
151
+ else:
152
+ prefix = 'useUpdate'
153
+ elif operation.http_method == 'DELETE':
154
+ prefix = 'useDelete'
212
155
  else:
213
- # Custom action
214
- return f"use{self._to_pascal_case(op_id)}"
156
+ prefix = 'use'
157
+
158
+ # For hooks, path is not critical but pass for consistency
159
+ return operation_to_method_name(operation_id, operation.http_method, prefix, self.base, operation.path)
215
160
 
216
161
  def _operation_to_fetcher_name(self, operation: IROperationObject) -> str:
217
162
  """Get corresponding fetcher function name (must match fetchers_generator logic)."""
218
- op_id = operation.operation_id
219
-
220
- # Must match fetchers_generator._operation_to_function_name() exactly
221
- if op_id.endswith("_list"):
222
- resource = op_id.removesuffix("_list")
223
- return f"get{self._to_pascal_case(resource)}List"
224
- elif op_id.endswith("_retrieve"):
225
- resource = op_id.removesuffix("_retrieve")
226
- # Add ById suffix to match fetchers_generator
227
- return f"get{self._to_pascal_case(resource)}ById"
228
- elif op_id.endswith("_create"):
229
- resource = op_id.removesuffix("_create")
230
- return f"create{self._to_pascal_case(resource)}"
231
- elif op_id.endswith("_partial_update"):
232
- resource = op_id.removesuffix("_partial_update")
233
- return f"partialUpdate{self._to_pascal_case(resource)}"
234
- elif op_id.endswith("_update"):
235
- resource = op_id.removesuffix("_update")
236
- return f"update{self._to_pascal_case(resource)}"
237
- elif op_id.endswith("_destroy"):
238
- resource = op_id.removesuffix("_destroy")
239
- return f"delete{self._to_pascal_case(resource)}"
163
+
164
+
165
+ # Remove cfg_ prefix but keep tag + resource (must match fetchers_generator exactly)
166
+ operation_id = operation.operation_id
167
+ if operation_id.startswith('django_cfg_'):
168
+ operation_id = operation_id.replace('django_cfg_', '', 1)
169
+ elif operation_id.startswith('cfg_'):
170
+ operation_id = operation_id.replace('cfg_', '', 1)
171
+
172
+ # Determine prefix (must match fetchers_generator exactly)
173
+ if operation.http_method == 'GET':
174
+ prefix = 'get'
175
+ elif operation.http_method == 'POST':
176
+ prefix = 'create'
177
+ elif operation.http_method in ('PUT', 'PATCH'):
178
+ if '_partial_update' in operation_id:
179
+ prefix = 'partialUpdate'
180
+ else:
181
+ prefix = 'update'
182
+ elif operation.http_method == 'DELETE':
183
+ prefix = 'delete'
240
184
  else:
241
- return self._to_camel_case(op_id)
185
+ prefix = ''
186
+
187
+ # Must match fetchers exactly, including path
188
+ return operation_to_method_name(operation_id, operation.http_method, prefix, self.base, operation.path)
242
189
 
243
190
  def _get_param_info(self, operation: IROperationObject) -> dict:
244
191
  """
@@ -385,22 +332,13 @@ class HooksGenerator:
385
332
 
386
333
  return keys
387
334
 
388
- def _to_pascal_case(self, snake_str: str) -> str:
389
- """Convert snake_case to PascalCase."""
390
- return ''.join(word.capitalize() for word in snake_str.split('_'))
391
-
392
- def _to_camel_case(self, snake_str: str) -> str:
393
- """Convert snake_case to camelCase."""
394
- components = snake_str.split('_')
395
- return components[0] + ''.join(x.capitalize() for x in components[1:])
396
-
397
335
  def generate_tag_hooks_file(
398
336
  self,
399
337
  tag: str,
400
338
  operations: list[IROperationObject],
401
339
  ) -> GeneratedFile:
402
340
  """
403
- Generate hooks file for a specific tag/resource.
341
+ Generate hooks file for a specific tag/resource using Jinja2 template.
404
342
 
405
343
  Args:
406
344
  tag: Tag name (e.g., "shop_products")
@@ -410,8 +348,7 @@ class HooksGenerator:
410
348
  GeneratedFile with hooks
411
349
  """
412
350
  # Separate queries and mutations & collect schema names
413
- query_hooks = []
414
- mutation_hooks = []
351
+ hooks = []
415
352
  schema_names = set()
416
353
 
417
354
  for operation in operations:
@@ -430,69 +367,28 @@ class HooksGenerator:
430
367
 
431
368
  # Generate hook
432
369
  if operation.http_method == "GET":
433
- query_hooks.append(self.generate_query_hook(operation))
370
+ hooks.append(self.generate_query_hook(operation))
434
371
  else:
435
- mutation_hooks.append(self.generate_mutation_hook(operation))
372
+ hooks.append(self.generate_mutation_hook(operation))
436
373
 
437
374
  # Get display name for documentation
438
375
  tag_display_name = self.base.tag_to_display_name(tag)
439
-
440
- # Build file content
441
- lines = []
442
-
443
- # Header
444
- lines.append("/**")
445
- lines.append(f" * SWR Hooks for {tag_display_name}")
446
- lines.append(" *")
447
- lines.append(" * Auto-generated React hooks for data fetching with SWR.")
448
- lines.append(" *")
449
- lines.append(" * Setup:")
450
- lines.append(" * ```typescript")
451
- lines.append(" * // Configure API once (in your app root)")
452
- lines.append(" * import { configureAPI } from '../../api-instance'")
453
- lines.append(" * configureAPI({ baseUrl: 'https://api.example.com' })")
454
- lines.append(" * ```")
455
- lines.append(" *")
456
- lines.append(" * Usage:")
457
- lines.append(" * ```typescript")
458
- lines.append(" * // Query hook")
459
- lines.append(" * const { data, error, mutate } = useShopProducts({ page: 1 })")
460
- lines.append(" *")
461
- lines.append(" * // Mutation hook")
462
- lines.append(" * const createProduct = useCreateShopProduct()")
463
- lines.append(" * await createProduct({ name: 'Product', price: 99 })")
464
- lines.append(" * ```")
465
- lines.append(" */")
466
-
467
- # Import types from schemas
468
- for schema_name in sorted(schema_names):
469
- lines.append(f"import type {{ {schema_name} }} from '../schemas/{schema_name}.schema'")
470
-
471
- lines.append("import useSWR from 'swr'")
472
- lines.append("import { useSWRConfig } from 'swr'")
473
- lines.append("import * as Fetchers from '../fetchers'")
474
- lines.append("")
475
-
476
- # Query hooks
477
- if query_hooks:
478
- lines.append("// ===== Query Hooks (GET) =====")
479
- lines.append("")
480
- for hook in query_hooks:
481
- lines.append(hook)
482
- lines.append("")
483
-
484
- # Mutation hooks
485
- if mutation_hooks:
486
- lines.append("// ===== Mutation Hooks (POST/PUT/PATCH/DELETE) =====")
487
- lines.append("")
488
- for hook in mutation_hooks:
489
- lines.append(hook)
490
- lines.append("")
491
-
492
- content = "\n".join(lines)
376
+
377
+ # Get tag file name for fetchers import
378
+ folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
379
+ tag_file = folder_name
380
+
381
+ # Render template
382
+ template = self.jinja_env.get_template('hooks/hooks.ts.jinja')
383
+ content = template.render(
384
+ tag_display_name=tag_display_name,
385
+ tag_file=tag_file,
386
+ has_schemas=bool(schema_names),
387
+ schema_names=sorted(schema_names),
388
+ hooks=hooks
389
+ )
493
390
 
494
391
  # Get file path (use same naming as APIClient)
495
- folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
496
392
  file_path = f"_utils/hooks/{folder_name}.ts"
497
393
 
498
394
  return GeneratedFile(
@@ -502,35 +398,9 @@ class HooksGenerator:
502
398
  )
503
399
 
504
400
  def generate_hooks_index_file(self, module_names: list[str]) -> GeneratedFile:
505
- """Generate index.ts for hooks folder."""
506
- lines = []
507
-
508
- lines.append("/**")
509
- lines.append(" * SWR Hooks - React hooks for data fetching")
510
- lines.append(" *")
511
- lines.append(" * Auto-generated from OpenAPI specification.")
512
- lines.append(" * These hooks use SWR for data fetching and caching.")
513
- lines.append(" *")
514
- lines.append(" * Usage:")
515
- lines.append(" * ```typescript")
516
- lines.append(" * import { useShopProducts } from './_utils/hooks'")
517
- lines.append(" *")
518
- lines.append(" * function ProductsPage() {")
519
- lines.append(" * const { data, error } = useShopProducts({ page: 1 })")
520
- lines.append(" * if (error) return <Error />")
521
- lines.append(" * if (!data) return <Loading />")
522
- lines.append(" * return <ProductList products={data.results} />")
523
- lines.append(" * }")
524
- lines.append(" * ```")
525
- lines.append(" */")
526
- lines.append("")
527
-
528
- for module_name in module_names:
529
- lines.append(f"export * from './{module_name}'")
530
-
531
- lines.append("")
532
-
533
- content = "\n".join(lines)
401
+ """Generate index.ts for hooks folder using Jinja2 template."""
402
+ template = self.jinja_env.get_template('hooks/index.ts.jinja')
403
+ content = template.render(modules=module_names)
534
404
 
535
405
  return GeneratedFile(
536
406
  path="_utils/hooks/index.ts",
@@ -162,17 +162,22 @@ class ModelsGenerator:
162
162
  if ts_type.endswith(" | null"):
163
163
  ts_type = ts_type[:-7] # Remove " | null"
164
164
  else:
165
- # Get TypeScript type and remove | null suffix if present
165
+ # Get TypeScript type
166
166
  ts_type = schema.typescript_type
167
+ # Remove | null suffix to rebuild it properly based on schema.nullable
167
168
  if ts_type.endswith(" | null"):
168
169
  ts_type = ts_type[:-7] # Remove " | null"
169
170
 
170
171
  # Check if required
171
172
  is_required = name in required_fields
172
173
 
173
- # Optional marker - use for both non-required AND nullable fields
174
- # This converts Django's nullable=True to TypeScript's optional (undefined)
175
- optional_marker = "" if is_required and not schema.nullable else "?"
174
+ # Handle nullable and optional separately
175
+ # - nullable: add | null to type
176
+ # - not required: add ? optional marker
177
+ if schema.nullable:
178
+ ts_type = f"{ts_type} | null"
179
+
180
+ optional_marker = "" if is_required else "?"
176
181
 
177
182
  # Comment
178
183
  if schema.description:
@@ -0,0 +1,83 @@
1
+ """
2
+ Simple naming strategy for TypeScript code generation.
3
+
4
+ Strategy: Use full operation_id, remove tag prefix, convert to camelCase/PascalCase.
5
+ """
6
+
7
+ import re
8
+
9
+
10
+ def to_camel_case(s: str) -> str:
11
+ """Convert snake_case or kebab-case to camelCase."""
12
+ s = s.replace('-', '_')
13
+ parts = s.split('_')
14
+ if not parts:
15
+ return ''
16
+ return parts[0].lower() + ''.join(p.capitalize() for p in parts[1:])
17
+
18
+
19
+ def to_pascal_case(s: str) -> str:
20
+ """Convert snake_case or kebab-case to PascalCase."""
21
+ s = s.replace('-', '_')
22
+ return ''.join(p.capitalize() for p in s.split('_'))
23
+
24
+
25
+ def remove_tag_prefix(operation_id: str) -> str:
26
+ """
27
+ Remove common tag prefixes from operation_id.
28
+
29
+ Examples:
30
+ cfg_newsletter_campaigns_list -> newsletter_campaigns_list
31
+ django_cfg_accounts_login -> accounts_login
32
+ newsletter_campaigns_send_create -> newsletter_campaigns_send_create
33
+ """
34
+ # Remove cfg_ or django_cfg_ prefix
35
+ if operation_id.startswith('django_cfg_'):
36
+ return operation_id[11:] # len('django_cfg_')
37
+ elif operation_id.startswith('cfg_'):
38
+ return operation_id[4:] # len('cfg_')
39
+ return operation_id
40
+
41
+
42
+ def operation_to_method_name(
43
+ operation_id: str,
44
+ http_method: str,
45
+ prefix: str,
46
+ base_generator,
47
+ path: str = ''
48
+ ) -> str:
49
+ """
50
+ Simple naming: remove tag prefix, convert to camelCase/PascalCase.
51
+
52
+ Args:
53
+ operation_id: Full operation ID from OpenAPI
54
+ http_method: HTTP method (GET, POST, PUT, PATCH, DELETE)
55
+ prefix: Function prefix ('', 'get', 'create', 'use', etc.)
56
+ base_generator: Base generator instance (unused)
57
+ path: URL path (unused)
58
+
59
+ Returns:
60
+ Method name in camelCase (for client) or PascalCase (for fetchers/hooks)
61
+
62
+ Examples:
63
+ # Client methods (prefix='')
64
+ cfg_newsletter_campaigns_list -> newsletterCampaignsList
65
+ cfg_newsletter_campaigns_send_create -> newsletterCampaignsSendCreate
66
+ cfg_accounts_otp_request_create -> accountsOtpRequestCreate
67
+
68
+ # Fetchers (prefix='get')
69
+ cfg_newsletter_campaigns_list -> getNewsletterCampaignsList
70
+
71
+ # Hooks (prefix='use')
72
+ cfg_newsletter_campaigns_list -> useNewsletterCampaignsList
73
+ """
74
+ # Remove tag prefix
75
+ clean_id = remove_tag_prefix(operation_id)
76
+
77
+ # For client methods (no prefix): camelCase
78
+ if not prefix:
79
+ return to_camel_case(clean_id)
80
+
81
+ # For fetchers/hooks (with prefix): prefix + PascalCase
82
+ return f"{prefix}{to_pascal_case(clean_id)}"
83
+
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  from jinja2 import Environment
8
8
  from ...ir import IROperationObject
9
-
9
+ from .naming import operation_to_method_name
10
10
 
11
11
  class OperationsGenerator:
12
12
  """Generates TypeScript async operation methods."""
@@ -18,15 +18,19 @@ class OperationsGenerator:
18
18
 
19
19
  def generate_operation(self, operation: IROperationObject, remove_tag_prefix: bool = False, in_subclient: bool = False) -> str:
20
20
  """Generate async method for operation."""
21
- # Get method name
21
+
22
+
23
+ # Get method name using universal logic
24
+ # For client methods, we use empty prefix to get short names: list, create, retrieve
22
25
  operation_id = operation.operation_id
23
26
  if remove_tag_prefix and operation.tags:
24
27
  # Remove tag prefix using base class method
25
28
  tag = operation.tags[0]
26
29
  operation_id = self.base.remove_tag_prefix(operation_id, tag)
27
30
 
28
- # Convert snake_case to camelCase
29
- method_name = self._to_camel_case(operation_id)
31
+ # Use universal naming function with empty prefix for client methods
32
+ # Pass path to distinguish custom actions
33
+ method_name = operation_to_method_name(operation_id, operation.http_method, '', self.base, operation.path)
30
34
 
31
35
  # Request method prefix
32
36
  request_prefix = "this.client" if in_subclient else "this"
@@ -94,7 +98,9 @@ class OperationsGenerator:
94
98
  # Return type
95
99
  primary_response = operation.primary_success_response
96
100
  if primary_response and primary_response.schema_name:
97
- if operation.is_list_operation:
101
+ # Check if response is paginated (has 'results' field)
102
+ is_paginated = primary_response.schema_name.startswith('Paginated')
103
+ if operation.is_list_operation and not is_paginated:
98
104
  return_type = f"Models.{primary_response.schema_name}[]"
99
105
  else:
100
106
  return_type = f"Models.{primary_response.schema_name}"
@@ -238,8 +244,14 @@ class OperationsGenerator:
238
244
 
239
245
  # Handle response
240
246
  if operation.is_list_operation and primary_response:
241
- # Extract results from paginated response
242
- body_lines.append("return (response as any).results || [];")
247
+ # Check if response is paginated
248
+ is_paginated = primary_response.schema_name.startswith('Paginated')
249
+ if is_paginated:
250
+ # Return full paginated response object
251
+ body_lines.append("return response;")
252
+ else:
253
+ # Extract results from array response
254
+ body_lines.append("return (response as any).results || [];")
243
255
  elif return_type != "void":
244
256
  body_lines.append("return response;")
245
257
  else: