Skip to content

feat: add React-free ./model entry point - #24

Merged
mfal merged 1 commit into
mainfrom
feat/react-free-model-entry
Sep 28, 2026
Merged

mfal merged 1 commit into
mainfrom
feat/react-free-model-entry

Conversation

@mfal

@mfal mfal commented Sep 28, 2026

Copy link
Copy Markdown
Member

Why

Model definitions only need GhostMakerModel and getModelName, and both are
free of React. The main entry, however, pulls in react, react/jsx-runtime and
@tanstack/react-query as soon as it is imported. Any package that defines
models therefore depends on React, even when it never renders anything — for
example a framework-agnostic domain-model package meant to run in Node or a CLI.

What

  • New subpath @mittwald/react-ghostmaker/model, built like ./polytype.
    It exports GhostMakerModel, ghostMakerModel, getModelName, getModelId
    and the types GhostMakerModelMeta and DynamicModel. It shares the model
    registry with the main entry, so models defined through ./model resolve in
    React ghosts as before.
  • react and @tanstack/react-query are now optional peers. Only the main
    entry needs them, and anyone using it has React installed anyway. The version
    ranges are unchanged.
  • getModelName and getModelId moved from queries.ts to metaData.ts.
    queries.ts carries a type import from types.ts, which references React
    types; left there, model.d.ts would require @types/react in consumers
    compiling with skipLibCheck: false. queries.ts re-exports them, so the main
    entry is unchanged.
  • GhostMakerModelMeta and DynamicModel are now exported from the main
    entry too, so it keeps offering everything ./model does.
  • README: a section on when to import from ./model, and a note that React
    and TanStack Query are only required for the main entry.

Keeping it React-free

reactFreeEntries.test.ts walks the import graph of model.ts and
polytype.ts statically, type imports and export … from included, and fails
as soon as it reaches react, react/*, react-dom or
@tanstack/react-query. A self-test asserts that the walker does find React
behind the main entry, so it cannot pass vacuously.

Compatibility

Purely additive. The main entry's runtime exports and their signatures are
identical before and after (compared on the build output); the only additions
are the two type exports. Suggested release: 1.6.0.

Verification

  • pnpm test in the package: tsc, eslint, build, vitest (25/25), license check.
  • dist/esm/model.js loads only metaData.js and @sindresorhus/is;
    dist/types/model.d.ts only references type-fest.
  • Installed the packed tarball into an empty project without React: npm installs
    neither peer, ./model runs (decorator, getModelName, getModelId), and
    tsc --skipLibCheck false compiles a file using the decorator and
    GhostMakerModelMeta.

🤖 Generated with Claude Code

Framework-agnostic model code only needs the GhostMakerModel decorator and
the model name/id accessors, but the main entry loads react,
react/jsx-runtime and @tanstack/react-query. The new
@mittwald/react-ghostmaker/model entry exposes GhostMakerModel,
ghostMakerModel, getModelName, getModelId and the GhostMakerModelMeta and
DynamicModel types without importing React, not even in its type
declarations.

getModelName and getModelId move from queries.ts to metaData.ts, because
queries.ts depends on the React-bound types.ts. The main entry keeps
exporting them unchanged and now also exports the two types.

react and @tanstack/react-query become optional peer dependencies, as only
the main entry needs them. A test walks the import graph of the ./model
and ./polytype entries (including type-only imports) and fails as soon as
either reaches react, react-dom or @tanstack/react-query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mfal
mfal merged commit 9af0f9b into main Sep 28, 2026
1 check passed
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.

1 participant