Introduce @codama/fragments with JavaScript and Rust subpaths#994
Introduce @codama/fragments with JavaScript and Rust subpaths#994lorisleiva wants to merge 1 commit into
@codama/fragments with JavaScript and Rust subpaths#994Conversation
🦋 Changeset detectedLatest commit: 568e19d The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
a9c3d19 to
b4b8197
Compare
trevor-cortex
left a comment
There was a problem hiding this comment.
Summary
Introduces @codama/fragments as a new public package consolidating fragment, import-map, render-map, casing, path and filesystem primitives that the renderer packages had been duplicating. The root entrypoint ships only language-agnostic core (BaseFragment, createFragmentTemplate, mapFragmentContent, setFragmentContent, plus the casing / path / fs / renderMap utilities), with @codama/fragments/javascript and @codama/fragments/rust layering on concrete Fragment types, ImportMap shapes, and language-specific helpers. @codama/renderers-core becomes a thin layer that re-exports @codama/fragments and keeps only the renderer-specific writeRenderMapVisitor on top — existing importers see the same public names at the same paths.
Overall this is a well-executed extraction: behavior is preserved (everything moved is either renamed-file or pure pass-through), the new code is densely documented with JSDoc and good README coverage, and the test suite is thorough. The three-subpath split is the right call to avoid name clashes between the JS and Rust flavors of Fragment / ImportMap / fragment. Happy to approve.
Key things to watch out for
@codama/renderers-coresurface expansion. Switching from explicit named re-exports toexport * from '@codama/fragments'is purely additive but does add casing helpers (camelCase,pascalCase,snakeCase,kebabCase,titleCase,capitalize) — and now alsomapRenderMapFragment/mapRenderMapFragmentAsync— to therenderers-corepublic surface, none of which were exported before. Safe for a patch bump (no removals, no renames), and the changeset frames it correctly, but worth being aware of if downstream consumers had locally shadowed any of those names.- JS
ImportMapinner maps aren't deeply frozen.mergeImportMapsandaddToImportMapreturnObject.freeze(merged)where the inner per-moduleMap<UsedIdentifier, ImportInfo>is a plainMap. TheReadonlyMaptype protects callers at the TS layer, but runtime mutation isn't prevented. This matches the pre-existing pattern (and the Rust flavor) so it isn't a new risk, just a known one. importMapToStringsort within the relative-import group falls out ofString#localeCompare, which orders'../shared'before'./local'because'.'<'/'at position 1. The test pins this, but it's a less common convention than "parent imports above sibling imports" — keep an eye on snapshot/golden diffs in dependent renderers when they migrate.addToImportMap(Rust) is existing-wins on alias (skips adding when path is already present, preserving any prior alias), whilemergeImportMaps(Rust) is latest-wins on alias. Intentional —addToImportMapdoesn't take an alias arg, so it should never clobber one — but the two functions have opposite priority semantics on the same data, and that asymmetry is worth keeping in mind when reading combined call sites.
Notes for subsequent reviewers
- The new
casinghelpers in@codama/fragmentsdeliberately return plainstringrather than the brandedCamelCaseString/PascalCaseString/ … from@codama/node-types(to keep fragments dep-free). Sister renderers that currently use the branded types will need a small cast at their boundary when they migrate to@codama/fragmentsin follow-up PRs. tsup.config.tsoverrides the base config's single-entry assumption to emit three entries (root,/javascript,/rust) and disables splitting. The comment in the file explains why. Thefilesfield inpackage.jsonlistsdist/index.*,dist/javascript.*,dist/rust.*— make sure the build actually produces those exact basenames (tsup'sentryobject keys drive the output filenames; this should work, but worth sanity-checking the first published artifact).tsconfig.declarations.jsononlyincludes the three entrypoints (src/index.ts,src/javascript/index.ts,src/rust/index.ts) plussrc/types. Anything not transitively reachable from those won't get.d.tsemitted — currently fine since every module is reachable.- Worth verifying once on a non-Node platform (browser / react-native build) that the
core/path.tsrelativePaththrow-path actually fires the structuredCodamaErrorrather than getting tree-shaken into something weird; the test covers it but the published bundle is the real proof. renderers-core's only remaining test is a smoke check thatwriteRenderMapVisitoris a function. That's fine for this package's current scope (every other name is forwarded straight throughexport *and exercised in the fragments tests), but worth a follow-up to add an integration test that actually drives the visitor end-to-end once the dependent renderers migrate.- The
@codama/fragmentsREADME is excellent and largely doubles as the migration guide for the follow-up renderer PRs.
This PR introduces `@codama/fragments`, a public package for composing generated source code. A `Fragment` is a snippet of code paired with the imports it depends on; fragments compose through tagged templates that propagate both content and imports, so generators can build code top-down without threading imports manually. The package consolidates what the existing renderers (`@codama/renderers-js`, `@codama/renderers-rust`, `@codama/renderers-rust-cpi`, `@codama/renderers-demo`) have each been duplicating in their own `src/utils/`. Three subpaths are exposed: the root entrypoint ships only the language-agnostic primitives (`BaseFragment`, `createFragmentTemplate`, `mapFragmentContent`, `setFragmentContent`); `@codama/fragments/javascript` and `@codama/fragments/rust` each layer on a concrete `Fragment` type, an `ImportMap` shape suited to the language, and the helpers needed to build, merge, resolve, and stringify imports. Both flavors are immutable, frozen, and free-function-based. As part of this change, `@codama/renderers-core` gets a patch bump: its `src/fragment.ts` becomes a named re-export from `@codama/fragments`, so existing importers see the same names at the same path with no behavior change. Sister renderers keep their own `importMap.ts` and `fragment.ts` for now and can migrate to `@codama/fragments` in subsequent PRs.
b4b8197 to
568e19d
Compare

This PR introduces
@codama/fragments, a public package for composing generated source code. AFragmentis a snippet of code paired with the imports it depends on; fragments compose through tagged templates that propagate both content and imports, so generators can build code top-down without threading imports manually. The package consolidates what the existing renderers (@codama/renderers-js,@codama/renderers-rust,@codama/renderers-rust-cpi,@codama/renderers-demo) have each been duplicating in their ownsrc/utils/.Three subpaths are exposed: the root entrypoint ships only the language-agnostic primitives (
BaseFragment,createFragmentTemplate,mapFragmentContent,setFragmentContent);@codama/fragments/javascriptand@codama/fragments/rusteach layer on a concreteFragmenttype, anImportMapshape suited to the language, and the helpers needed to build, merge, resolve, and stringify imports. Both flavors are immutable, frozen, and free-function-based. As part of this change,@codama/renderers-coregets a patch bump: itssrc/fragment.tsbecomes a named re-export from@codama/fragments, so existing importers see the same names at the same path with no behavior change. Sister renderers keep their ownimportMap.tsandfragment.tsfor now and can migrate to@codama/fragmentsin subsequent PRs.