@visionary_software/contrax-range-check
v2026.5.10
Published
Contrax third-party plugin: InRange Enforcement + @rangeBounds JSDoc typedef registration.
Readme
contrax-range-check — Parameterized [lower, upper) Bounds Plugin (TypeScript line)
A third-party Enforcement for contrax that enforces numeric range constraints at compile time. Mirrors the Java line's InRange + @RangeBounds pair using JSDoc-typedef registration and JSON-shape bounds.
This is the TypeScript line. The Java line lives on java; the Kotlin K2/FIR port on kotlin.
Install
bun add @visionary_software/contrax-range-check
npm i @visionary_software/contrax-range-check
pnpm add @visionary_software/contrax-range-check
yarn add @visionary_software/contrax-range-checkDirect dependency: @visionary_software/contrax-annotations for the Enforcement type, violate, and withDetail. Peer: typescript.
Usage
Pull the @rangeBounds typedef into your program by importing the package for its side effects (no runtime symbols are imported — only the source file's presence in the program matters), then annotate parameters or return values:
import "@visionary_software/contrax-range-check"; // makes @rangeBounds resolvable
class TemperatureSensor {
/**
* @postcondition InRange
* @rangeBounds {"lower": -273, "upper": 1000}
*/
readCelsius(): number {
return this.celsius;
}
scoreEntry(/** @precondition InRange @rangeBounds {"lower": 0, "upper": 100} */ score: number): void {
// …
}
}
new TemperatureSensor().scoreEntry(150); // tsc ERROR: @rangeBounds violated at parameter #1 ('score' of type number) of TemperatureSensor.scoreEntry: 150 not in [0, 100)API
InRange: Enforcement
Demands a value at the seam in the half-open interval [lower, upper). Three flavors of violation:
| Use site | Result |
|---|---|
| Literal null at a number \| null seam | Diagnostic with detail null literal |
| Numeric literal at or above upper, or below lower | Diagnostic with detail <value> not in [lower, upper) |
| Unary-minus over a numeric literal whose negation is out of range | Same frame |
Non-literal expressions (variables, computed values) pass silently — contrax is a compile-time framework and cannot reason about runtime values.
InRange throws on a misconfigured @rangeBounds tag (missing lower or upper) with the message:
@rangeBounds requires both 'lower' and 'upper' numeric attributes; got: <comment text>Loud-on-misconfig, mirror of the Java line's IllegalStateException — the throw propagates out of the transformer and fails the build, which is the desired surface for a programmer error.
How @rangeBounds resolves
Two pieces collaborate to turn a use-site @rangeBounds tag into an InRange invocation:
- The package's
src/index.tsdeclares a JSDoc typedef whose name israngeBounds. The typedef itself is decorated with@contract,@precondition InRange, and@postcondition InRange— that meta-annotation block is what registers the use-site tag's enforcement policy. - At every contrax-tagged use site, apt's
tagsOnRegistrationSiteprimitive walks the program's source files looking for a typedef whose name matches the use-site tag. When the consumer's program containsrange-check's source file (via the side-effect import), the walk finds the typedef and returns its tags; the dispatcher reads@precondition InRange, looksInRangeup in the enforcements map, and invokes it with the use-site call site bound.
Without the side-effect import, no source file in the program declares @rangeBounds, the lookup returns the empty tag list, and use-site @rangeBounds tags silently no-op.
TypeScript peculiarities
Side-effect import is mandatory. Most TypeScript packages export named symbols you
import { X } from "..."; this one is consumed purely for its side effects on the source-file set. The transformer's contract-discovery walksprogram.getSourceFiles(); the package's source file must be in that set or use-site tags silently no-op.import "@visionary_software/contrax-range-check"is the idiomatic way to pull a source file into the program's set without binding any runtime symbols. Tree-shaking is not a concern — the typedef block has no runtime payload, only AST contributions the transformer reads at build time.CJS bundle ships alongside ESM. Same constraint as
contrax-enforcements: the transformer's discovery doesrequire()on thepackage.json#contrax.enforcementsentry at build time, and Node'srequirecannot consume native ESM. So this package builds two artifacts —dist/index.js(ESM, what consumers import for the side effect) anddist/index.cjs(CJS, what the transformer loads at discovery time). If you fork this package or write your own typedef-bearing Enforcement bundle, you must publish both formats and pointpackage.json#contrax.enforcementsat the.cjs.
License
GPL-3.0-or-later. See COPYING. Contact Visionary Software Solutions for commercial licensing.
