@@ -86,39 +86,39 @@ the verbs, filters, sort/pagination, and wire format are uniform.
8686` apiPrefix ` (default ` /api ` , set in project config) flows to both the server routes
8787and the client fetch URLs.
8888
89- The ` <entity> ` collection segment is NOT currently uniform across ports — a known
90- divergence, not a subtlety. Each port composes it with its own rule , and no rule
91- is more correct than another :
89+ The ` <entity> ` collection segment is the ** entity name ` snake_case ` d and then
90+ pluralized ** — one rule, identical in all five ports , and derived from the NAME,
91+ never from the physical ` @table ` :
9292
93- | Port | ` <entity> ` segment rule | ` OrderSummary ` → | Decided by |
94- | ---| ---| ---| --- |
95- | TypeScript (entity) | snake_case, then pluralize (underscores) | ` order_summaries ` | ` resourcePath ` in ` codegen-ts ` 's ` entity-ui-descriptor.ts ` |
96- | TypeScript ( ` source.rdb ` projection) | pluralize → snake_case → hyphens | ` order-summaries ` | same function, projection branch |
97- | C# | pluralize, then lowercase — no separator | ` ordersummaries ` | ` CSharpNaming.RoutePath ` ( ` MetaObjects.Codegen ` ) |
98- | Java | lowercase + ` "s" ` | ` ordersummarys ` | ` SpringNaming.pluralLowercase ` ( ` codegen-spring ` ) |
99- | Kotlin | lowercase + ` "s" ` | ` ordersummarys ` | ` KotlinNaming.pluralLowercase ` ( ` codegen-kotlin ` ) |
100- | Python | lowercase + ` "s" ` | ` ordersummarys ` | ` plural_lowercase ` ( ` apidocs/naming.py ` ) |
101-
102- A single-word name hides all of this ( ` Author ` → ` authors ` under every rule
103- above); a MULTI-word name must be checked against the serving port's rule before
104- assuming a path — a wrong guess is a silent 404, not a build error. The trap is
105- cross-port by construction: the generated web- client hooks and grids build their
106- fetch URLs from the TypeScript ` $path ` , so for a multi-word name a React/TanStack
107- client calls a path a C#, Java, Kotlin, or Python backend does not mount.
108-
109- ** TypeScript additionally splits entity from projection, deliberately ** : the two
110- TypeScript rows compose in a different ORDER as well as a different separator ,
111- and both spellings are already mounted, so neither may be "tidied" into the
112- other — unifying them would be a breaking route rename for existing projection
113- consumers. The split is deliberate and grandfathered, not an oversight.
114-
115- These segment spellings are slated for UNIFICATION in a separate follow-up
116- change — one rule across ports, a breaking route rename for the non-TypeScript
117- ports — which will collapse this table back to a single rule. Until it lands,
118- this section deliberately documents today's shipped behaviour: a doc describing
119- an unshipped state is worse than one describing a messy shipped one. Read the
120- deciding function for the port you are wiring; do not guess a path and do not
121- "fix" one port's spelling to match another's .
93+ | Name | Segment | Rule |
94+ | ---| ---| ---|
95+ | ` Author ` | ` authors ` | a single regular word takes ` s ` |
96+ | ` PostCategory ` | ` post_categories ` | multi-word: the capitals carry the word boundary |
97+ | ` Address ` | ` addresses ` | ending ` s ` / ` x ` / ` z ` / ` ch ` / ` sh ` takes ` es ` |
98+ | ` Category ` | ` categories ` | consonant + ` y ` becomes ` ies ` |
99+ | ` Day ` | ` days ` | a VOWEL before the ` y ` does not |
100+ | ` HTTPServer ` | ` http_servers ` | a run of capitals stays together until the final one that begins a word |
101+
102+ An ` object.projection ` uses the same rule, so ` OrderSummary ` is at
103+ ` /order_summaries ` either way. The generated web-client hooks and grids build
104+ their fetch URLs from the TypeScript ` $path ` , and every backend now mounts that
105+ same spelling, so a React/TanStack client works against any port's server.
106+
107+ ** This is a change, and it was a breaking one. ** Each port used to spell the
108+ segment differently and they agreed only on single regular words — which was
109+ every collection base in the corpus, so all five lanes were green while
110+ ` OrderSummary ` was served at four different URLs: ` /order_summaries ` (TS entity) ,
111+ ` /order-summaries ` (TS projection), ` /ordersummaries ` (C#) and ` /ordersummarys `
112+ (Java, Kotlin, Python). A project whose entity names are all single regular words
113+ saw nothing move. Any multi-word or irregular-plural name had its collection URL
114+ renamed, and clients had to follow.
115+
116+ The rule is gated, not just documented: ` fixtures/api-contract-conformance/m2m/ `
117+ declares ` PostCategory ` — multi-word AND ending consonant+ ` y ` , so it separates
118+ every spelling the ports used to produce — and asserts both retired spellings
119+ 404, on each port's reference AND generated lane. The JVM ports share one
120+ implementation ( ` RouteNaming ` in ` codegen-base ` ); the acronym case is pinned by
121+ unit test in each port, since no corpus entity carries one .
122122
123123| Verb | Path | Purpose |
124124| ---| ---| ---|
0 commit comments