Skip to content

feat: in-memory generator context seam (fix OpenAPI/AsyncAPI in-memory drift)#420

Merged
jonaslagoni merged 2 commits into
mainfrom
feat/in-memory-generator-seam
Jul 16, 2026
Merged

feat: in-memory generator context seam (fix OpenAPI/AsyncAPI in-memory drift)#420
jonaslagoni merged 2 commits into
mainfrom
feat/in-memory-generator-seam

Conversation

@ALagoni97

@ALagoni97 ALagoni97 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Why

In-memory code generation is already a first-class capability of this package — the browser bundle (src/browser/generate.ts, the website playground) generates from an in-memory spec string, and loadAsyncapiFromMemory / loadJsonSchemaFromMemory already existed. The problem: every in-memory caller (the browser bundle, and any programmatic consumer) re-implemented document loading by hand instead of reusing one path, so they silently drifted from codegen generate. Two real drifts found:

  1. OpenAPI — in-memory paths dereferenced but never called reflectComponentSchemaNames (the fix(openapi): preserve component schema names through dereferencing #416 fix), so an array of $ref items lost the component name and generated items: ItemsItem[] instead of items: TransactionModel[].
  2. AsyncAPI — in-memory callers used a bare Parser without the shared schema parsers (Avro/OpenAPI/RAML/Protobuf) and with looser diagnostics than loadAsyncapiDocument.

What

Make in-memory generation a single, shared path rather than something each consumer re-derives:

  • loadOpenapiFromMemory(specString) — shares the exact dereference + reflectComponentSchemaNames normalization with the local-file branch of loadOpenapiDocument.
  • realizeInMemoryGeneratorContext({ configuration, specificationDocument }) — the in-memory counterpart of the already-public realizeGeneratorContext; realizes config and dispatches to load{Openapi,Asyncapi,JsonSchema}FromMemory on inputType, using the same root-level virtual paths as the browser path.
  • loadJsonSchemaFromMemory now also accepts a raw spec string (backward compatible with the object form).
  • Export the seam + the three in-memory loaders from the package entry (src/index.ts).
  • src/browser/generate.ts now routes OpenAPI and JSON Schema through the shared loadOpenapiFromMemory / loadJsonSchemaFromMemory instead of its own hand-rolled parse — deleting the duplicated OpenAPI parse (which had the same ItemsItem bug) and JSON Schema parse.
  • Ignore the local-only playground/ harness in eslint (mirrors .gitignore).

Why this belongs in the CLI (not a downstream-only hook)

Anticipating the fair question "is this just for an external consumer?" — no:

  • This package is explicitly a library as well as a binary (CLAUDE.md: "CLI and library live in the same package under src/"). It already exports realizeConfiguration, runGenerators, realizeGeneratorContext, and the loaders. realizeInMemoryGeneratorContext simply completes that surface as the in-memory twin of the existing public realizeGeneratorContext.
  • In-memory generation is an existing CLI feature, not a new one. The browser bundle/playground already does it — and was carrying the same ItemsItem bug. This PR fixes that bug and removes the browser's duplicated parsing.
  • The shared loaders now have two independent in-CLI consumers — the browser bundle (src/browser/generate.ts) and the Node in-memory seam (realizeInMemoryGeneratorContext). This is a de-duplication of logic the CLI already had in three places, not scaffolding for one caller.
  • The alternative is worse. Leaving each caller to assemble the context from raw loaders is exactly the pattern that produced both drifts here (the inputType→loader dispatch and the missing reflectComponentSchemaNames). Centralizing it is the fix.

Deliberate asymmetry (documented, not an oversight)

The browser's AsyncAPI path stays on loadAsyncapiFromMemoryBrowser rather than the shared seam. The Node loadAsyncapiFromMemory depends on @asyncapi/parser plus the Avro/OpenAPI/RAML/Protobuf schema parsers, which the browser build intentionally shims away for bundle size/compat. OpenAPI and JSON Schema have no such constraint, so those are shared. The seam itself is Node-side (mirroring the Node-only realizeGeneratorContext); the browser assembles its own context because it must inject a browser-shimmed AsyncAPI parser and drives renderGraph directly.

Testing

  • npm run build, npm run build:browser, and npm run lint all clean.
  • Full npm test: 656 passed, 78 snapshots.
  • Verified end-to-end: the safepay OpenAPI example now generates items?: TransactionModel[] (was ItemsItem[]).
  • Backward compatibility: loadJsonSchemaFromMemory still accepts the object form (existing browser/tests unchanged).

🤖 Generated with Claude Code

…Schema

Downstream in-memory callers (the platform's generateInMemory/preview and
the browser bundle) re-implemented document loading instead of reusing the
CLI loaders, so they silently drifted from `codegen generate`. Two concrete
drifts:

- OpenAPI: in-memory paths dereferenced without calling
  reflectComponentSchemaNames, so array items lost their component name and
  collapsed to `ItemsItem[]` instead of e.g. `TransactionModel[]`.
- AsyncAPI: in-memory callers used a bare Parser without the shared schema
  parsers (Avro/OpenAPI/RAML/Protobuf) and looser diagnostics handling.

Expose a single in-memory seam so callers never re-implement parsing:

- add loadOpenapiFromMemory, sharing the exact dereference +
  reflectComponentSchemaNames normalization with the local-file loader
- add realizeInMemoryGeneratorContext, the in-memory counterpart of
  realizeGeneratorContext, dispatching to the load*FromMemory loaders
- accept a raw spec string in loadJsonSchemaFromMemory for symmetry
- export the seam and the three in-memory loaders from the public entry
- fix the browser generate path to also call reflectComponentSchemaNames
- ignore the local-only playground harness in eslint (mirrors .gitignore)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ALagoni97
ALagoni97 requested a review from jonaslagoni as a code owner July 16, 2026 10:32
@vercel

vercel Bot commented Jul 16, 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 16, 2026 11:23am
the-codegen-project-mcp Ready Ready Preview, Comment Jul 16, 2026 11:23am

The browser generate path hand-rolled its own OpenAPI parse (parse +
dereference + reflectComponentSchemaNames) and JSON Schema string parsing,
duplicating the Node in-memory loaders. Route both through the shared
loadOpenapiFromMemory / loadJsonSchemaFromMemory so the browser playground
and the Node in-memory seam use one implementation and cannot drift.

AsyncAPI stays on loadAsyncapiFromMemoryBrowser: the Node loader pulls in
@asyncapi/parser plus the Avro/OpenAPI/RAML/Protobuf schema parsers, which
the browser bundle deliberately shims away.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jonaslagoni
jonaslagoni merged commit 7d7b566 into main Jul 16, 2026
17 checks passed
@jonaslagoni

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.77.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