@visionary_software/contrax-enforcements
v2026.5.10
Published
Built-in contrax Enforcements — IsNotNull, IsNotBlank, IsPositive — for the TypeScript port.
Readme
contrax-enforcements — Built-in Contract Enforcements (TypeScript line)
Ready-to-use Enforcement implementations for the contrax Design by Contract framework: IsNotNull, IsNotUndefined, IsNotVoid, MustBePresent, IsNotBlank, IsPositive. Each is literal-shape only — flags what's obviously wrong at the AST level, stays silent on anything that can't be decided at compile time.
This is the TypeScript line. The Java line lives on java; the Kotlin K2/FIR port on kotlin.
Install
bun add @visionary_software/contrax-enforcements
npm i @visionary_software/contrax-enforcements
pnpm add @visionary_software/contrax-enforcements
yarn add @visionary_software/contrax-enforcementsDirect dependency: @visionary_software/contrax-annotations for the Enforcement type and violate(). Peer: typescript.
API (so far)
The plurality of absence
Tony Hoare called null his billion-dollar mistake. TypeScript inherited that warning and shipped at least five distinct AST-recognizable ways to produce a null-or-undefined value at runtime — the literal null keyword, the bare undefined identifier, the void <expr> operator, as-casts to null/undefined, and the legacy angle-bracket <null>/<undefined> casts. Each is genuinely distinct programmer intent. So instead of cramming everything into one Enforcement, this module ships one Enforcement per absence concept plus a MustBePresent combinator that fires every check at one seam.
IsNotNull: Enforcement
Demands a non-null value at the seam. Flags every form whose static AST shape resolves to null:
- The literal
nullkeyword. - An
as-cast whose target type is thenullliteral type (x as null). - The legacy angle-bracket cast (
<null>x).
Type-position null is a LiteralTypeNode wrapping a NullLiteral (unlike undefined, which is a direct UndefinedKeyword keyword type) — the as/<> recognition unwraps that layer.
Passes silently on identifier references and computed values whose runtime value contrax cannot inspect.
IsNotUndefined: Enforcement
Demands a non-undefined value at the seam. Flags every form whose static AST shape resolves to undefined:
- The bare
undefinedidentifier (undefinedis a binding, not a keyword — recognition is structural viats.isIdentifier(expr) && expr.escapedText === "undefined"). - An
as-cast whose target type is theundefinedkeyword type (x as undefined). - The legacy angle-bracket cast (
<undefined>x).
Passes silently on other identifier references (a binding shadowing undefined would slip past, but TS strict mode flags the shadow as an error already).
IsNotVoid: Enforcement
Demands a non-void expression at the seam. Flags any void <expr> use site. The void operator always evaluates to undefined regardless of operand, so ts.isVoidExpression(site.expression) is sufficient recognition; the operand is never inspected.
MustBePresent: Enforcement
The combinator. Sequentially invokes IsNotNull, IsNotUndefined, and IsNotVoid on the same CallSite. The natural reading for "this value must actually be there" — useful when the seam's nominal type doesn't allow absence and any of TS's many absence forms would silently betray the contract.
Composition is safe because every leaf Enforcement opens with the withDetail skip-guard (see below) and each leaf only matches its own AST shape, so at most one inner call fires per absence site (no double-flagging).
IsNotBlank: Enforcement
Demands a non-blank string at the seam. Flags empty- ("") and whitespace-only (" ", "\t\n") string literals; passes silently on non-string literals, identifier references, and computed values.
"Blank" matches the JVM's String.isBlank() semantics: the literal text after .trim().length === 0 flags. Any literal carrying at least one non-whitespace character passes.
IsPositive: Enforcement
Demands a strictly positive numeric value at the seam. Two AST shapes count as non-positive numeric literals and flag:
- A bare
NumericLiteralwhose parsed value is<= 0(e.g.0,0.0). - A
PrefixUnaryExpression(MinusToken, NumericLiteral)— TypeScript parses-1as two AST nodes (a unary-minus around1), not as a single signed literal. Any unary-minus over a numeric literal is non-positive: the operand is zero-or-positive, so its negation is zero-or-negative.
Passes silently on non-numeric literals, identifier references, and any computed expression contrax cannot inspect.
The withDetail skip guard
Every Enforcement opens with:
if (site.kind === "withDetail") return;A CallSite is either a leaf use site or a withDetail-wrapped one. The wrapper exists so an Enforcement can decorate another's site with a clarifying suffix in the diagnostic — the leaf already decided the site is a violation, the wrapper just adds context. Re-running the literal-shape check on the wrapper would double-flag, so every Enforcement skips it.
The same guard is what makes Enforcements composable: if you write a custom Enforcement that delegates to one of the built-ins and wraps the result with withDetail(site, "expected non-blank tenant name"), the built-in will not re-fire on your wrapper.
TypeScript peculiarities
CJS bundle ships alongside ESM. The transformer's package.json discovery does
require()at build time on Node (tspc runs on the Node runtime), and Node'srequirecannot consume native ESM. So this package builds two artifacts —dist/index.js(ESM, for downstream TS/Node consumers) anddist/index.cjs(CJS, what the transformer loads at discovery time). Thepackage.json#contrax.enforcementsfield points at the.cjsso the transformer's discovery walk finds an entry it canrequire. If you fork this package or write your own Enforcement bundle, you must publish a CJS artifact and pointcontrax.enforcementsat it — an ESM-only bundle silently no-ops at discovery.One Enforcement per absence form. TypeScript's plurality of absences (literal
null, bareundefinedidentifier,void <expr>operator,as-casts to either, legacy angle-bracket casts) has no JVM/Kotlin counterpart — the source-languageIsNotNullhad only one shape to recognize. Rather than overloadIsNotNullto catch every form, this module ships separate concept-level Enforcements (IsNotNull,IsNotUndefined,IsNotVoid) and aMustBePresentcombinator. The cast forms fold into their matching literal Enforcement (x as nullis morally equivalent tonull), so the surface is three Enforcements + one combinator rather than five. Adopters pick the precise contract they mean.
License
GPL-3.0-or-later. See COPYING. Contact Visionary Software Solutions for commercial licensing.
