Skip to content

feat: add channels organization option (flat / tag / path)#398

Merged
jonaslagoni merged 11 commits into
mainfrom
organizing_strategy
Jul 10, 2026
Merged

feat: add channels organization option (flat / tag / path)#398
jonaslagoni merged 11 commits into
mainfrom
organizing_strategy

Conversation

@jonaslagoni

@jonaslagoni jonaslagoni commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Overview

Adds an organization: 'flat' | 'tag' | 'path' option to the TypeScript channels generator. It controls how generated functions are surfaced in the barrel index.tsthe per-protocol <protocol>.ts function code never changes, only the re-export shape does.

  • flat (default) — today's behavior, byte-for-byte identical.
  • tag — groups functions one level deep under their API tag (operation tag → v3 channel tag → untagged bucket). Leaf names verbatim.
  • path — nests functions by URL path / channel address segments; leaf is the HTTP method for OpenAPI and a clean action verb (publish, subscribe, jetStreamPublish, …) for AsyncAPI, mirroring the method leaf. On a leaf collision the unique function name is used as a fallback so nothing is dropped.
// flat (default)
await http_client.updatePet({ ... });
// tag
await http_client.pet.updatePet({ ... });
// path
await http_client.pet.put({ ... });                 // OpenAPI: method leaf
await nats.user.signedup.publish({ ... });           // AsyncAPI: action-verb leaf
await nats.user.signedup.jetStreamPublish({ ... });

Applies to both OpenAPI (http_client) and all 7 AsyncAPI protocols.

Breaking change?

No. organization defaults to 'flat', which reproduces current output exactly (guarded by an unchanged snapshot + byte-identical runtime regeneration). New shapes are opt-in. Minor (feat:).

Approach (Expected Output First / TDD)

Implemented across 8 commits following the approved plan:

  1. Additive Zod field + tags/pathSegments/method metadata on render types.
  2. Tagged AsyncAPI v3 fixture, 4 dedicated codegen configs → src/*-organization, hand-written expected reference barrels, RED runtime specs.
  3. RED unit tests (flat regression guard + tag/path/metadata).
  4. Populate grouping metadata at the render call sites (shared resolveGroupingMetadata / attachGroupingToRenders); OpenAPI walker + all 7 protocols.
  5. finalizeGeneration branches via a new renderChannelIndex helper (groupByTag / groupByPath / renderObjectLiteral).
  6. Docs "Organization" section + example README.
  7. Regenerated schemas + options-table row + new snapshots.
  8. Focused unit tests for the barrel helpers.

Review follow-up

Two points raised in review, addressed in a follow-up commit:

  • Clean AsyncAPI path leaf. The leaf was the verbose function name (nats.user.signedup.publishToSendUserSignedup), which defeats the point of nesting. It's now a derived action verb (nats.user.signedup.publish) mirroring the OpenAPI method leaf, via a ChannelFunctionTypes → verb map. Collisions fall back to the unique function name (previously the second function was silently dropped), and a path segment that collides with an existing leaf now warns + skips instead of clobbering.
  • Decoupled the organization plumbing. The metadata plumbing was copy-pasted per protocol: 7 byte-identical addRendersToExternal functions + the resolveGroupingMetadata/attachGroupingToRenders pair wired at ~13 sites. Consolidated into a single shared addRendersToExternal (protocol-keyed) and a single attachGroupingMetadata helper in utils.ts. The grouping field set now lives in one place — a new protocol gets organization support for free. ~210 lines of duplication removed.

Verification

  • npm run build ✓ · npm run lint (max-warnings 0) ✓
  • Unit: 639 tests / 78 snapshots ✓ — existing flat / tag / OpenAPI-path snapshots unchanged (plumbing refactor is output-identical)
  • CI green across the board: BlackBox, all 7 runtime protocol suites + Regular Tests, and NodeJS tests on ubuntu / macos / windows all pass. The earlier local prepare:pr lockfile drift in test/runtime/typescript/ was machine-local only — CI's runtime prep + suites pass cleanly.

🤖 Generated with Claude Code

jonaslagoni and others added 8 commits July 8, 2026 20:54
…ption

Add organization: 'flat' | 'tag' | 'path' to zodTypescriptChannelsGenerator
(default 'flat', no behavior change) and optional tags/pathSegments/method
grouping metadata to TypeScriptChannelRenderedFunctionType and
SingleFunctionRenderType. Consumed only by finalizeGeneration in later phases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tagged AsyncAPI v3 fixture (asyncapi-organization.json), 4 dedicated
per-style codegen configs generating into src/{openapi,asyncapi}-{tag,path}-organization,
wire generate:organization + test:organization scripts, hand-written expected
reference barrels under test/channels/organization/expected/, and 4 runtime
specs asserting the grouped surface. Generation runs clean (still flat); the
14 organization assertions fail for the right reason (grouped exports absent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an organization describe block to channels.spec.ts: flat regression guard
(passes, byte-identical namespace barrel), plus tag/path grouped-shape and
grouping-metadata assertions that fail because finalizeGeneration/metadata
population are not yet implemented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add resolveGroupingMetadata + attachGroupingToRenders + splitAddressSegments
helpers to channels/utils.ts. Populate tags/pathSegments on each render across
all 7 AsyncAPI protocols (per-operation in generateForOperations, per-channel
in generateForChannels) and tags/pathSegments/method for OpenAPI in the
processOperation walker; copy the fields through each addRendersToExternal.
Grouping fields added to HttpRenderType. Metadata unit test passes; flat output
unchanged (15 existing channel snapshots green). tag/path barrel branching lands
in Phase 5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…art 2)

Extract renderChannelIndex into channels/utils.ts (with groupByTag, groupByPath,
renderObjectLiteral) and call it from finalizeGeneration. flat stays byte-identical
to today; tag/path emit a grouped 'export const <protocol> = {...} as const' literal
per protocol (tag: one level, untagged bucket; path: nested by segments, method leaf
for OpenAPI / function-name leaf for AsyncAPI, with collision warning). All 4 unit
org tests and 14 runtime org specs green; 17 channel unit tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an Organization subsection to docs/generators/channels.md (flat/tag/path,
tag-source precedence, path leaf method-vs-function, v2 no-channel-tags caveat,
before/after call sites) and an Organizing subsection to the openapi-http-client
example README. Options-table row is regenerated from Zod in Phase 7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regenerate schemas/configuration-schema-0.json and -0-with-docs.json from Zod
(only the organization enum added). Add the organization row to the channels
options table. New openapi-tag/openapi-path barrel snapshots recorded; all
existing flat snapshots unchanged (627 tests, 78 snapshots green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add channels-organization-utils.spec.ts covering splitAddressSegments,
groupByTag (untagged bucket), groupByPath (method vs function-name leaf,
collision keep-first), renderObjectLiteral (nesting + identifier quoting), and
renderChannelIndex (flat/tag/empty). Grouping attach logic is already shared via
resolveGroupingMetadata + attachGroupingToRenders; no any, explicit return types
throughout. 637 unit tests + 78 snapshots green, lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jonaslagoni jonaslagoni requested a review from ALagoni97 as a code owner July 8, 2026 21:26
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
the-codegen-project Ready Ready Preview, Comment Jul 10, 2026 1:22pm
the-codegen-project-mcp Ready Ready Preview, Comment Jul 10, 2026 1:22pm

…h leaf

Addresses two review points on the channels `organization` option.

1. AsyncAPI `path` leaf is now a derived action verb (publish, subscribe,
   jetStreamPublish, …) mirroring the OpenAPI method leaf, instead of the
   verbose function name. On a leaf collision the unique function name is used
   as a fallback so no function is silently dropped; a path segment that
   collides with an existing leaf now warns and skips instead of clobbering.

2. Consolidated the duplicated organization plumbing: a single shared
   addRendersToExternal (protocol-keyed) replaces 7 byte-identical per-protocol
   copies plus the inline openapi block, and a single attachGroupingMetadata
   helper folds the resolveGroupingMetadata + attachGroupingToRenders pair used
   at ~13 call sites. The grouping field set now lives in one place; a new
   protocol gets organization support for free.

Updated docs, Zod description + regenerated schemas, groupByPath unit tests,
and the AsyncAPI-path runtime expected/generated/spec artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/codegen/generators/typescript/channels/openapi.ts
#	src/codegen/types.ts
@jonaslagoni

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 0.74.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants