99from jinja2 import Environment , FileSystemLoader , select_autoescape
1010
1111from ts_oas_generator import constants
12- from ts_oas_generator .generator .filters import FILTERS , ts_camel_case , ts_pascal_case , ts_type
12+ from ts_oas_generator .generator .filters import FILTERS , ts_camel_case , ts_kebab_case , ts_pascal_case , ts_type
1313from ts_oas_generator .parser .oas_parser import OASParser
1414
1515# Type aliases for clarity
@@ -120,7 +120,6 @@ def __init__(self, template_dir: Path | None = None) -> None:
120120 self .env = self ._create_environment ()
121121
122122 def _create_environment (self ) -> Environment :
123- """Create and configure Jinja2 environment."""
124123 env = Environment (
125124 loader = FileSystemLoader (str (self .template_dir )),
126125 autoescape = select_autoescape (["html" , "xml" ]),
@@ -131,12 +130,10 @@ def _create_environment(self) -> Environment:
131130 return env
132131
133132 def render (self , template_name : str , context : TemplateContext ) -> str :
134- """Render a single template."""
135133 template = self .env .get_template (template_name )
136134 return template .render (** context )
137135
138136 def render_batch (self , template_map : dict [Path , tuple [str , TemplateContext ]]) -> FileMap :
139- """Render multiple templates."""
140137 return {path : self .render (template , context ) for path , (template , context ) in template_map .items ()}
141138
142139
@@ -145,25 +142,25 @@ class SchemaProcessor:
145142
146143 def __init__ (self , renderer : TemplateRenderer ) -> None :
147144 self .renderer = renderer
145+ self ._wire_to_canonical : dict [str , str ] = {}
146+ self ._camel_to_wire : dict [str , str ] = {}
148147
149148 def generate_models (self , output_dir : Path , schemas : Schema ) -> FileMap :
150- """Generate TypeScript model files from schemas."""
151149 models_dir = output_dir / constants .DirectoryName .SRC / constants .DirectoryName .MODELS
152150 files : FileMap = {}
153151
154152 # Generate individual model files
155153 for name , schema in schemas .items ():
156154 context = self ._create_model_context (name , schema , schemas )
157155 content = self .renderer .render (constants .MODEL_TEMPLATE , context )
158- files [models_dir / f"{ name .lower ()} { constants .MODEL_FILE_EXTENSION } " ] = content
156+ file_name = f"{ ts_kebab_case (name )} { constants .MODEL_FILE_EXTENSION } "
157+ files [models_dir / file_name ] = content
159158
160- # Generate comprehensive AlgokitSignedTransaction model
161159 # TODO(utils-ts): Delete this temporary model once utils-ts is part of the monorepo
162160 files [models_dir / f"algokitsignedtransaction{ constants .MODEL_FILE_EXTENSION } " ] = self .renderer .render (
163161 "models/algokitsignedtransaction.ts.j2" , {}
164162 )
165163
166- # Generate barrel export
167164 files [models_dir / constants .INDEX_FILE ] = self .renderer .render (
168165 constants .MODELS_INDEX_TEMPLATE ,
169166 {"schemas" : schemas , "include_temp_signed_txn" : True },
@@ -172,7 +169,6 @@ def generate_models(self, output_dir: Path, schemas: Schema) -> FileMap:
172169 return files
173170
174171 def _create_model_context (self , name : str , schema : Schema , all_schemas : Schema ) -> TemplateContext :
175- """Create context for model template rendering."""
176172 is_object = self ._is_object_schema (schema )
177173 properties = self ._extract_properties (schema ) if is_object else []
178174
@@ -188,21 +184,19 @@ def _create_model_context(self, name: str, schema: Schema, all_schemas: Schema)
188184
189185 @staticmethod
190186 def _is_object_schema (schema : Schema ) -> bool :
191- """Check if schema represents an object type."""
192187 is_type_object = schema .get (constants .SchemaKey .TYPE ) == constants .TypeScriptType .OBJECT
193188 has_properties = constants .SchemaKey .PROPERTIES in schema
194189 has_composition = any (
195190 k in schema for k in [constants .SchemaKey .ALL_OF , constants .SchemaKey .ONE_OF , constants .SchemaKey .ANY_OF ]
196191 )
197192 return (is_type_object or has_properties ) and not has_composition
198193
199- @staticmethod
200- def _extract_properties (schema : Schema ) -> list [dict [str , Any ]]:
201- """Extract properties from an object schema."""
194+ def _extract_properties (self , schema : Schema ) -> list [dict [str , Any ]]:
202195 properties = []
203196 required_fields = set (schema .get (constants .SchemaKey .REQUIRED , []))
204197
205198 for prop_name , prop_schema in (schema .get (constants .SchemaKey .PROPERTIES ) or {}).items ():
199+ self ._register_rename (prop_name , prop_schema )
206200 properties .append (
207201 {
208202 "name" : prop_name ,
@@ -213,6 +207,19 @@ def _extract_properties(schema: Schema) -> list[dict[str, Any]]:
213207
214208 return properties
215209
210+ def _register_rename (self , wire_name : str , schema : dict [str , Any ]) -> None :
211+ rename_value = schema .get (constants .X_ALGOKIT_FIELD_RENAME )
212+ if not isinstance (rename_value , str ) or not rename_value :
213+ return
214+
215+ # Preserve first occurrence to avoid accidental overrides from conflicting specs
216+ self ._wire_to_canonical .setdefault (wire_name , rename_value )
217+ self ._camel_to_wire .setdefault (ts_camel_case (rename_value ), wire_name )
218+
219+ @property
220+ def rename_mappings (self ) -> tuple [dict [str , str ], dict [str , str ]]:
221+ return self ._wire_to_canonical , self ._camel_to_wire
222+
216223
217224class OperationProcessor :
218225 """Processes OpenAPI operations and generates API services."""
@@ -609,6 +616,7 @@ def generate(
609616 files .update (self .schema_processor .generate_models (output_dir , all_schemas ))
610617 files .update (self .operation_processor .generate_service (output_dir , ops_by_tag , tags , service_class ))
611618 files .update (self ._generate_client_files (output_dir , client_class , service_class ))
619+ files .update (self ._generate_rename_map (output_dir ))
612620
613621 return files
614622
@@ -642,6 +650,7 @@ def _generate_runtime(
642650 core_dir / "json.ts" : ("base/src/core/json.ts.j2" , context ),
643651 core_dir / "msgpack.ts" : ("base/src/core/msgpack.ts.j2" , context ),
644652 core_dir / "casing.ts" : ("base/src/core/casing.ts.j2" , context ),
653+ core_dir / "codecs.ts" : ("base/src/core/codecs.ts.j2" , context ),
645654 # Project files
646655 src_dir / "index.ts" : ("base/src/index.ts.j2" , context ),
647656 output_dir / "package.json" : ("base/package.json.j2" , context ),
@@ -669,6 +678,23 @@ def _generate_client_files(self, output_dir: Path, client_class: str, service_cl
669678
670679 return self .renderer .render_batch (template_map )
671680
681+ def _generate_rename_map (self , output_dir : Path ) -> FileMap :
682+ """Render rename map supporting vendor rename extensions."""
683+ wire_to_canonical , camel_to_wire = self .schema_processor .rename_mappings
684+
685+ core_dir = output_dir / constants .DirectoryName .SRC / constants .DirectoryName .CORE
686+ return {
687+ core_dir / "rename-map.ts" : (
688+ self .renderer .render (
689+ "base/src/core/rename-map.ts.j2" ,
690+ {
691+ "wire_to_canonical" : wire_to_canonical ,
692+ "camel_to_wire" : camel_to_wire ,
693+ },
694+ )
695+ )
696+ }
697+
672698 @staticmethod
673699 def _extract_class_names (package_name : str ) -> tuple [str , str ]:
674700 """Extract client and service class names from package name."""
0 commit comments