Skip to content

fix: correct the peer ranges, prove both ends in CI, and finish adapter parity - #56

Merged
vannt-dev merged 4 commits into
developfrom
fix/peer-ranges-and-adapter-parity
Sep 3, 2026
Merged

fix: correct the peer ranges, prove both ends in CI, and finish adapter parity#56
vannt-dev merged 4 commits into
developfrom
fix/peer-ranges-and-adapter-parity

Conversation

@vannt-dev

Copy link
Copy Markdown
Owner

Closes the last items standing between develop and a 1.6.0 release. Two commits.

1. fix: declare the peer ranges the packages can actually satisfy

Angular's declared floor was wrong, and would fail at runtime. The package declared @angular/core / @angular/common as >=14 <22, but the form store imports signal and computed, which Angular introduced in 16. npm accepted an install on 14 or 15 and the package then threw on import. Now >=16 <22, so the bad install is refused up front — verified by running the new script against Angular 15 and watching npm reject it.

Vue moves to ^3.2.0, and gains the cleanup that motivates it. useDynamicForm now aborts whatever is in flight when the owning effect scope is disposed; without it an unmounted form held its request open until the response arrived. getCurrentScope / onScopeDispose are Vue 3.2, and the getCurrentScope() guard covers calling the composable outside a scope, which the tests do. Test-first: the new test fails without the cleanup.

Neither range was ever exercised at its floor — the suites only run against whatever the workspace installs. Two new scripts fix that, beside the React one that has existed since 1.5.0, all three in the CI verify job:

Script What it proves
verify-vue-peer-range.js Server-renders the packed tarballs under Vue 3.2 and the newest 3.x
verify-angular-peer-range.js Installs them against Angular 16 and 21; the package imports, its components evaluate, the registry is shared with core

Angular is import-level rather than render-level on purpose: the published fesm2022 needs the CLI's linker to instantiate a component. Import-and-wire is the level that breaks across majors — precisely how a floor of 14 survived years of signal().

@angular/platform-browser-dynamic is gone. Angular 21 deprecates it. Removed from the package's devDependencies and from the demo app (which bootstraps with bootstrapApplication and never used it); the test setup initialises through @angular/platform-browser/testing.

2. feat: re-export the new core helpers from every adapter

collectFieldPaths, indexGroupPathMap and the ValidationContext type shipped in core but reached none of the adapters, so typing a validator's context argument meant importing @dynamic-field-kit/core alongside the adapter — the drift the curated re-export lists exist to prevent. Adapter READMEs list them and document the corrected peer ranges. scripts/add-dts-extensions.js records that ng-packagr 21 emits one rolled-up d.ts with nothing left to rewrite, so it is a no-op now and stays only as a guard.

Verification

Gate Result
lint / format-check / typecheck pass / pass / pass
core / react / vue / angular 151 / 143 / 135 / 110
smoke / test:scripts / core test:types 2 / 44 / 9, no type errors
build, all four packages pass
verify scripts 5/5
Vue peer range 3.2.47 and 3.5.42 both render
Angular peer range 16.2.12 and 21.2.22 both import and share a registry
arethetypeswrong on all four tarballs core/react/vue clean; angular clean but for the inherent CJSResolvesToESM
Real node16 ESM consumer against the packed tarballs tsc exit 0

The Angular package declared `@angular/core` and `@angular/common` as
`>=14 <22` while its form store imports `signal` and `computed`, which
Angular introduced in 16. npm accepted an install on 14 or 15 and the
package then threw on import - the manifest promised something it had not
been able to do for a long time. The range is now `>=16 <22`, so the same
install is refused up front. Verified both ways: Angular 16 and 21 import
and share one registry with core, and an install against 15 is now rejected.

Vue moves from `^3.0.0` to `^3.2.0`, because `useDynamicForm` now aborts
whatever is still in flight when the owning effect scope is disposed.
Without it an unmounted form held its request open until the response came
back. `getCurrentScope` and `onScopeDispose` are both Vue 3.2, and the
`getCurrentScope()` guard is for calling the composable outside a scope,
which the tests do.

Neither range was ever exercised at its floor - the suites only ever run
against whatever the workspace installs. `scripts/verify-vue-peer-range.js`
server-renders the packed tarballs under Vue 3.2 and the newest 3.x;
`scripts/verify-angular-peer-range.js` installs them against Angular 16 and
21 and checks the package imports, its components evaluate, and the registry
is shared. Both run in the CI verify job beside the React one. Angular is
import-level rather than render-level on purpose: the published fesm2022
needs the CLI's linker to instantiate a component, and import-and-wire is
the level that breaks across majors - which is precisely how a floor of 14
survived years of `signal()`.

Angular 21 deprecates `@angular/platform-browser-dynamic`. It is gone from
the package's devDependencies and from the demo app, which never used it
(it bootstraps with `bootstrapApplication`), and the test setup initialises
through `@angular/platform-browser/testing` instead.

Removing that devDependency meant regenerating the lockfile, and npm on
Windows prunes optional entries the Linux runners need while doing it - it
dropped @emnapi/core, @emnapi/runtime, @noble/hashes and yaml, which is
enough for Unknown command: "ci"

Did you mean this?
  npm ci # Clean install a project
To see a list of supported npm commands, run:
  npm help to refuse the lockfile on CI. They are restored here.
`collectFieldPaths`, `indexGroupPathMap` and the `ValidationContext` type
shipped in core with 1.6.0 but reached none of the adapters, so typing a
validator's `context` argument meant importing `@dynamic-field-kit/core`
alongside the adapter - the exact thing the curated re-export list exists to
avoid, and a drift the three lists are supposed to stay free of.

The adapter READMEs list them, document the corrected peer ranges, and
`scripts/add-dts-extensions.js` records that ng-packagr 21 emits one
rolled-up d.ts with no relative specifiers left to rewrite, so the step is a
no-op now and stays only as a guard.
Importing the package under Angular 16 proves it resolves and evaluates, but
not the thing that actually breaks a consumer across majors. The published
fesm2022 ships partial declarations, and a consumer's build links them with
*its* Angular. If building the library on a newer Angular raised their
`minVersion`, every consumer below that version would fail in the linker
while installing and importing perfectly well - the failure would surface in
their app, never here.

So the check runs the floor's own `@angular/compiler-cli` linker over the
bundle and asserts nothing is left partial. Against the ng-packagr 21 output
the Angular 16 linker consumes all of it: the declarations still carry
`minVersion` 12 and 14, because ng-packagr stamps what the emitted code
needs, not the compiler that emitted it.

Only the floor is linked. A newer linker accepting an older declaration is
the direction that was never in doubt.
@vannt-dev
vannt-dev force-pushed the fix/peer-ranges-and-adapter-parity branch from 9aa0439 to 8451c33 Compare September 3, 2026 19:10
… exist

The docs carry dozens of copy-paste import statements and nothing checked
them, so a rename lands, the READMEs keep the old name, and the first person
to find out is a reader whose editor cannot resolve it.

This reads every `import { ... } from '@dynamic-field-kit/*'` inside a fenced
code block and asserts each name is exported, resolving the export list from
each package's built .d.ts through the TypeScript compiler so type-only
exports count. Prose is ignored - only fenced blocks are checked, because
they are what people copy - and a package that has not been built is skipped
rather than treated as exporting nothing.

Proven non-vacuous the way the peer range checks are: adding an import of a
name that does not exist exits 1 and names the file and line.

The docs pass as they stand. The one thing worth knowing about the extractor
is that the brace span must exclude braces - `import { h } from 'vue'` above
an import of this package was otherwise swallowed into one statement, and
every name in the first import was reported missing.
@vannt-dev
vannt-dev merged commit 5ae6f70 into develop Sep 3, 2026
10 checks passed
@vannt-dev
vannt-dev deleted the fix/peer-ranges-and-adapter-parity branch September 3, 2026 19:17
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