Skip to content

Introduce @codama/fragments with JavaScript and Rust subpaths#994

Open
lorisleiva wants to merge 1 commit into
mainfrom
05-08-introduce_codama_fragments_with_javascript_and_rust_subpaths
Open

Introduce @codama/fragments with JavaScript and Rust subpaths#994
lorisleiva wants to merge 1 commit into
mainfrom
05-08-introduce_codama_fragments_with_javascript_and_rust_subpaths

Conversation

@lorisleiva
Copy link
Copy Markdown
Member

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.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 11, 2026

🦋 Changeset detected

Latest commit: 568e19d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@codama/fragments Minor
@codama/renderers-core Patch

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

Copy link
Copy Markdown
Member Author

lorisleiva commented May 11, 2026

@lorisleiva lorisleiva force-pushed the 05-08-introduce_codama_fragments_with_javascript_and_rust_subpaths branch from a9c3d19 to b4b8197 Compare May 11, 2026 16:11
@lorisleiva lorisleiva marked this pull request as ready for review May 12, 2026 08:24
@lorisleiva
Copy link
Copy Markdown
Member Author

@trevor-cortex

Copy link
Copy Markdown

@trevor-cortex trevor-cortex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-core surface expansion. Switching from explicit named re-exports to export * from '@codama/fragments' is purely additive but does add casing helpers (camelCase, pascalCase, snakeCase, kebabCase, titleCase, capitalize) — and now also mapRenderMapFragment / mapRenderMapFragmentAsync — to the renderers-core public 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 ImportMap inner maps aren't deeply frozen. mergeImportMaps and addToImportMap return Object.freeze(merged) where the inner per-module Map<UsedIdentifier, ImportInfo> is a plain Map. The ReadonlyMap type 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.
  • importMapToString sort within the relative-import group falls out of String#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), while mergeImportMaps (Rust) is latest-wins on alias. Intentional — addToImportMap doesn'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 casing helpers in @codama/fragments deliberately return plain string rather than the branded CamelCaseString / 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/fragments in follow-up PRs.
  • tsup.config.ts overrides the base config's single-entry assumption to emit three entries (root, /javascript, /rust) and disables splitting. The comment in the file explains why. The files field in package.json lists dist/index.*, dist/javascript.*, dist/rust.* — make sure the build actually produces those exact basenames (tsup's entry object keys drive the output filenames; this should work, but worth sanity-checking the first published artifact).
  • tsconfig.declarations.json only includes the three entrypoints (src/index.ts, src/javascript/index.ts, src/rust/index.ts) plus src/types. Anything not transitively reachable from those won't get .d.ts emitted — currently fine since every module is reachable.
  • Worth verifying once on a non-Node platform (browser / react-native build) that the core/path.ts relativePath throw-path actually fires the structured CodamaError rather 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 that writeRenderMapVisitor is a function. That's fine for this package's current scope (every other name is forwarded straight through export * 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/fragments README 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.
@lorisleiva lorisleiva force-pushed the 05-08-introduce_codama_fragments_with_javascript_and_rust_subpaths branch from b4b8197 to 568e19d Compare May 13, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants