fix: make generated code compile, unblank the blackbox tier, correct the docs - #434
Merged
Merged
Conversation
…the docs
Addresses the three ideas in
.claude/thoughts/shared/improvements/2026-07-29-2227-improvements.md.
Generated code did not compile (idea 1)
- `ajv-formats` is imported by every validating payload/header model but was
documented nowhere and declared in no package.json. Document it alongside
`ajv` on the payloads/headers/channels pages, and declare it in the five
examples, the runtime suite, and the blackbox project template that import it.
- `{default as addFormats}` resolves to the module namespace under
`moduleResolution: node16`/`nodenext`, so `addFormats(ajv)` was not callable
(TS2349). Import the module and unwrap `.default` at the call site — verified
against ajv-formats v2 and v3, under node/node16/nodenext/bundler.
- `importExtension` never reached the imports Modelina renders between models,
so those stayed extensionless and failed TS2835 in exactly the projects the
option exists for. Apply it at the single Modelina emission seam.
- The companion interface was re-exported as a value, which breaks under
`isolatedModules: true` (TS1205, Next.js/SWC/esbuild). Split it into
`export type`.
- Header unions (a channel whose messages each declare headers) referenced the
expanded `A | B | C` type without importing its members, and emitted
`A | B | C.unmarshal(...)` — a bitwise-or expression, not a call. Import union
header models as a namespace, like payload unions, and give them module-level
`unmarshal`/`marshal`.
- The client generator's subscribe wrappers omitted the `headers` callback slot
the channel functions declare, and never imported the header models.
- NATS reply passed a string as `ErrorOptions` to `new Error(...)`.
The blackbox tier compiled nothing (idea 2)
Since the in-memory generator seam, `runGenerators` returns files rather than
writing them; the harness never got the write call, so all 216 outputs were an
empty `index.ts` and every pair passed regardless. Write the files, assert they
reach disk, and wire up the missing OpenAPI loader arm. All 214 pairs pass on
real generated output. Also wires `codegen-node16.mjs` — an orphan config with
no npm script — into the runtime `generate` chain.
Docs described an API the generator does not have (idea 3)
`import { Protocols }` was removed but still taught on seven pages (and used by
an example whose documented `npm run demo` threw), while the migration guide
told readers to stop using it. Fixed that, the positional call sites across the
protocol pages, `postPingPostRequest` -> `postPostPing`, the AMQP/EventSource/
WebSocket/client signatures, the invalid `custom.md` snippet, the
`amqp_publish`/`amqp_consume` values that generate nothing, `mapType`'s default,
and the OpenAPI channels/client support claims. Documents the payloads and
headers options, which had no table at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Declaring `ajv-formats` moved the runtime suite off a transitively hoisted 2.1.1 and onto the ^3.0.1 the examples already pin, which surfaced a real behaviour difference: v3 follows RFC 3339 `full-time` and requires a time offset, so `format: time` rejects a bare `"10:30:00"` that v2 accepted. The three FormatTime cases asserting bare local times were encoding the behaviour of an undeclared, accidentally resolved version. Point them at offset-bearing values, add explicit coverage for a numeric offset and for the now-invalid no-offset case, and document the change where users read about validation dependencies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
🎉 This PR is included in version 0.80.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue: none (sourced from
.claude/thoughts/shared/improvements/2026-07-29-2227-improvements.md, ideas 1–3)What
Three connected problems, fixed in dependency order:
tscaftercodegen generatefailed. Five distinct defects, all reproduced before and after.index.ts, so every pair passed regardless of what was generated — which is why (1) shipped. It now compiles real output; all 214 pairs pass.Why
ajv-formatsis imported by every validating payload and header model, but it was declared in nopackage.jsonin the repo and listed on no docs page. Followingdocs/generators/payloads.mdand installing onlyajvproduced eightTS2307s and a runtimeMODULE_NOT_FOUND. The repo's own suites only passed because a transitively hoisted copy happened to be present.That went unnoticed because the tier meant to catch it was inert. Since the in-memory generator seam (
7d7b566),runGeneratorsreturns files rather than writing them, and the blackbox harness never got the write call. It generated in memory, discarded the result, found nosrc/, created one containing an emptyindex.ts, and compiled that. Its project template ismoduleResolution: node16with noajv— it was configured to catch every one of these failures on the first pair.Unblanking it also surfaced two bugs shipped by
e18b928: a channel whose messages each declare headers emittedA | B | C.unmarshal(...)— a bitwise-or expression, not a call — over identifiers the file never imported; and the NATS client's subscribe wrappers omitted theheaderscallback slot the channel functions declare.Changes
Generated code now compiles
ajv-formatsdocumented alongsideajvon the payloads/headers/channels pages, and declared in the five examples, the runtime suite, and the blackbox project template that import it.{default as addFormats}resolves to the module namespace undermoduleResolution: node16/nodenext, soaddFormats(ajv)was not callable (TS2349×8) — the source comment claimed otherwise. Import the module and unwrap.defaultat the call site.importExtensionnever reached the imports Modelina renders between models (an object model and the enum it was split into), so those stayed extensionless and failedTS2835in exactly the projects the option exists for. Setting it explicitly did not help either. Applied at the single Modelina emission seam (src/codegen/output/modelina.ts) and threaded through payloads/parameters/headers/models.isolatedModules: true(TS1205— Next.js, SWC, esbuild). Split intoexport type.unmarshal/marshalvia the union preset. NewgetHeaderTypeAndModulekeeps type positions (<Name>Module.<Name>) and value positions (<Name>Module.unmarshal) distinct across all seven protocols.headerscallback parameter and import the header models;headerTypeis carried on the rendered-function metadata, and the channels render now exposesheaderRender.ErrorOptionstonew Error(...).The blackbox tier compiles real output
writeGeneratedFilesis called afterrunGenerators, with an assertion that every generated file reaches disk.writeGeneratedFilesandloadOpenapiare exported from the package root.inputType === 'openapi'arm is wired up (the harness previously had noloadOpenapibranch, so OpenAPI fixtures would silently no-op), andinputPathresolves againsttest/blackbox/likedocumentPathalready did.codegen-node16.mjshad no npm script and ran nowhere, so the ESM surface was never regenerated. Wired into the runtimegeneratechain.Docs match the generator
import { Protocols }was removed but still taught on seven pages, whiledocs/migrations/v0.mdtold readers to stop using it — andexamples/ecommerce-asyncapi-channelsstill used it, so its documentednpm run demothrew.postPingPostRequest→postPostPing(verified by generating from the doc's own example spec); AMQP/EventSource/WebSocket/client signatures; thecustom.mdsnippet that was not valid JavaScript;amqp_publish/amqp_consume, which are not in the enum and silently generate no file;mapType's documented default; and the OpenAPI channels/client support claims indocs/generators/channels.mdandREADME.md.Dependency behaviour change — see Notes.
Testing
npm run buildnpm run lint(exit 0)npm test(720 passing, 1 skipped; 47 snapshots updated for the two intentional generated-code changes; two new specs coverapplyImportExtensionand the header-union resolution)isolatedModulestest/runtime/typescript/src/node16type-checks undermodule: Node16+allowImportingTsExtensionstsconfig.jsontype-checks —ecommerce-asyncapi-client,typescript-library,typescript-nextjs,jsonschema-models(the first three failed before)npm run prepare:pras a single command — not run; itsnpm cisteps fail on pre-existing lockfile drift. Every stage was run directly instead (build,generate:readme:toc/generate:commands/generate:schema/format/generate:examples, lint, tests, runtime regeneration). See Notes.Notes
Behaviour change —
ajv-formatsv3format: time. Declaring the dependency moved the runtime suite off the transitively hoisted2.1.1and onto the^3.0.1the examples already pinned, which exposed a real difference: v3 follows RFC 3339full-timeand requires a time offset, so"10:30:00"is now rejected while"10:30:00Z"and"10:30:00+02:00"are accepted. Verified directly against both versions. The threeFormatTimecases asserting bare local times were encoding the behaviour of an undeclared, accidentally resolved version; they now assert v3 behaviour, with added coverage for a numeric offset and for the no-offset case. Documented indocs/generators/payloads.md; users needing the lenient behaviour can pinajv-formats@^2.Scope beyond the three ideas. The header-union bug and the client callback omission were shipped by
e18b928and are not in the improvements file. Unblanking the blackbox tier surfaced them and they blocked "examples compile", so they were fixed here rather than parked inKNOWN_FAILING.prepare:prand generated assets.npm run generate:assetsis not idempotent on this checkout independently of these changes:markdown-tocappends a trailing newline to four docs files and rewrites thedocs/usage.mdToC (becausegenerate:readme:tocruns beforegenerate:commands), anddocs/usage.mdembeds a platform string that differs by OS. Those were reverted rather than folded into this PR — worth a separate fix.Lockfiles.
package-lock.jsonregenerated for the five examples and the runtime suite whose dependencies changed.Follow-ups not taken here (recorded in the improvements file): modern JSON Schema (
$schema: 2020-12+$defs) is rejected withInput is not a JSON Schema, so it cannot be processed.;discriminatornever drives generatedunmarshal()(src/codegen/modelina/presets/union.ts:100-110is unreachable and its emitted line is not valid TypeScript); subscribe callbacks are positional for NATS/Kafka but object-destructured for MQTT/AMQP/WebSocket, contrary to the project's own rule; and.claude/thoughts/shared/progress/2026-07-23-operation-channel-path-filtering-status.jsonreports 16/16 phases complete for a feature absent fromsrc/.