← Docs

Model naming

How CodeForge names the payload models it generates from allOf, and why it no longer produces AllOf_N in a model name.

CodeForge names a generated payload model after its schema's own title (or, failing that, its $id). Only a schema with neither falls back to a structural name built from the path it was reached by. allOf used to leak that structural path into names that were otherwise perfectly readable — that no longer happens for the common case, and is muted for the rest.

Single-member allOf is flattened away

An OpenAPI document commonly wraps a single reference in allOf so it can attach sibling keywords next to it — most often a description on a response body that is otherwise just a $ref:

responses:
  '201':
    content:
      application/json:
        schema:
          description: The created widget
          allOf:
            - $ref: '#/components/schemas/Widget'

Before generating anything, CodeForge flattens a single-member allOf into its one member, merging the sibling keywords (description here) losslessly into it. properties and required are combined rather than overwritten, since both the member and its siblings routinely declare their own share of each. The flattened schema is exactly as if allOf had never wrapped it — including the member's own title, which is what names the model. The Widget schema's title wins, and the model is generated as Widget, not as a structural name derived from allOf's first member.

A multi-member allOf is a real intersection of two or more schemas, not sibling annotations on one, and is left untouched — only its members are flattened, recursively, wherever a single-member allOf occurs inside them.

AllOf_N is stripped from the rest

A multi-member allOf still produces one model per unnamed schema nested inside it, and that model's structural name used to carry the member's index literally: AllOf_0, AllOf_1, and so on. Modelina infers that name from the member's own position in the document, not from the response it happens to sit inside — so a response combining a base schema (allOf[0], an id property) with an untitled status enum (allOf[1]) used to name that enum AllOf_1Status, with no trace of the surrounding response in it at all.

CodeForge now strips every AllOf_<n> fragment from a generated model name before it is used, so that same enum generates as plain Status — every bit as unique, with none of the structural noise about which allOf member it came from.

Collisions are still resolved the same way

Stripping AllOf_N can make two models that used to have distinct names collide. Two operations that each return a response shaped this way — an id at allOf[0] and an untitled status enum at allOf[1] — both strip to the same bare name, Status. Naming has never guaranteed uniqueness on its own; the resolution CodeForge already applies to every other name collision applies here too, unchanged:

  1. The colliding model is prefixed with its nearest containing model's name.
  2. If that is still taken, a numeric suffix is appended, starting at 1.

The first response generated keeps the bare name, Status. The second is renamed to SessionsUpdateResponse_200Status, and the run logs why:

Model name collision on 'Status': claimed by 'SessionsCreateResponse' and
generated again with different content for 'SessionsUpdateResponse'.
Renaming the nested 'SessionsUpdateResponse' model to
'SessionsUpdateResponse_200Status'.

Both outcomes are deterministic: regenerating the same document always produces the same names, and the generation run's logs name every collision it resolved, so you can always trace a model back to why it is named the way it is.

Names differing only in case are collisions too

Windows and macOS resolve MetaData.ts and Metadata.ts to the same file; Linux does not. CodeForge treats two model names as colliding the moment they are the same after lower-casing, whether that happens between two nested models in one schema tree or between two models generated on different runs — so a package that builds locally on Linux CI cannot still fail to compile on a customer's Windows machine.

The same resolution as above applies: the model reached first keeps its name, and the second is prefixed with its nearest containing model's name, falling back to a numeric suffix. A response nesting both a MetaData and a Metadata object renames the second and logs why:

Model name collision on 'Metadata': differs only in case from the nested
model 'MetaData' already claimed in the same schema. Renaming to
'ContainerMetadata'.

A rename always reaches every place the model's name is used: the file it is written to, the identifier declared in that file, every export a package's root module re-exports it under, and every generated consumer of the model — including the HTTP client and server code that types an operation's parameters and headers against it. A model that is renamed after another part of the document already generated it — the cross-run case above — updates the earlier-generated files' imports too, so nothing is left pointing at a name no file declares.

Two property names that format to one identifier

CodeForge names a generated property after its JSON Schema key, formatted to camelCase. Two keys that are spelled differently but format to the same identifier — team_Id and team_id, both teamId — used to collapse into one property, silently dropping whichever the formatter processed first. A parameter class or payload model referencing the dropped property by its original key no longer compiled.

The first property in schema order keeps the plain identifier. Each later property whose formatted name is already taken gets the shortest free numeric suffix, with no separator: teamId, teamId1, teamId2. The wire name — the raw key sent and read on the wire — is never affected; only the generated TypeScript identifier changes. The run logs which key had to give way:

Property name collision on 'GetTaskParameters': 'team_id' formats to
'teamId', which another property already claims. Renaming it to 'teamId1'.

This applies everywhere CodeForge constrains an object's properties: payload models, parameter classes and header models alike.

A parameters or headers class yields to a payload model of the same name

Payload models come from the document's own schemas; a parameter or header class's name is the engine's invention — <operationId>Parameters or <operationId>Headers. When the two coincide, both used to land in one generated file: the payload as an interface, the parameter or header class as a class, and tsc reported both TS2300 (duplicate identifier) and TS2693 (a type used as a value) on the same name.

CodeForge now claims TypeScript identifiers for the payloads, parameters and headers presets in one shared space, in a fixed priority: payloads first, since the document named them; parameters next; headers last. A parameter or header class whose name is already claimed is renamed with the same shortest-free-numeric-suffix scheme every other collision uses — its companion <Name>Interface, when it has one, moves with it, since both ship from one file:

An operation settleCharge next to a component schema SettleChargeParameters renders:

payloads/SettleChargeParameters.ts   → interface SettleChargeParameters
parameters/SettleChargeParameters1.ts → class SettleChargeParameters1
                                         interface SettleChargeParameters1Interface

The run logs why:

The parameters class 'SettleChargeParameters' for operation 'settleCharge'
collides with a payload model's TypeScript identifier. Renaming it to
'SettleChargeParameters1'.

The rename reaches every consumer the same way every other rename in this document does: the emitted file, the barrel export, and every channels and client import and call site. A parameter or header class is only accepted once both it and its companion (when it has one) are free — a name that is merely unclaimed by a payload but still taken by an earlier parameter or header class keeps searching. Wire names are never affected: the raw path, query and header parameter names sent and read on the wire come straight from the document, whichever TypeScript identifier the class ends up with.

A document with no such collision generates exactly as it did before.

Migrating

This is a one-time, breaking rename. There is no flag to opt back into the old, AllOf_N-bearing names, the pre-collision case-only names, the overwritten property names, or a parameter or header class's pre-collision name — regenerate your SDK and update any code that imports a model, a parameter or header class, or reads a property by its old name.