Skip to content

sdougbrown/umpire

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

213 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›‚ Umpire

Rule the form. Play the field.

Umpire is a declarative field-availability engine. Define fields, declare rules between them, and Umpire tells you which fields are in play β€” and which stale values just fell out. It answers a structural question, not a validation question: given the current values and conditions, what should be enabled right now?

Forms are the most common use case, but Umpire works anywhere state fits a plain object with interdependent options β€” game boards, config panels, pricing calculators, permission matrices. If it has fields and rules, Umpire can call the game.

Docs β€’ GitHub

Coverage Status

Quick Example

import { enabledWhen, requires, umpire } from '@umpire/core'

const signupUmp = umpire({
  fields: {
    email:           { required: true, isEmpty: (v) => !v },
    password:        { required: true, isEmpty: (v) => !v },
    confirmPassword: { required: true, isEmpty: (v) => !v },
    referralCode:    {},
    companyName:     {},
    companySize:     {},
  },
  rules: [
    requires('confirmPassword', 'password'),
    enabledWhen('companyName', (_values, cond) => cond.plan === 'business', {
      reason: 'business plan required',
    }),
    enabledWhen('companySize', (_values, cond) => cond.plan === 'business', {
      reason: 'business plan required',
    }),
    requires('companySize', 'companyName'),
  ],
})

const availability = signupUmp.check(
  { email: 'alex@example.com', password: 'hunter2' },
  { plan: 'personal' },
)

availability.companyName
// { enabled: false, required: false, reason: 'business plan required', reasons: ['business plan required'] }

const fouls = signupUmp.play(
  {
    values: {
      email: 'alex@example.com',
      password: 'hunter2',
      companyName: 'Acme',
      companySize: '50',
    },
    conditions: { plan: 'business' },
  },
  {
    values: {
      email: 'alex@example.com',
      password: 'hunter2',
      companyName: 'Acme',
      companySize: '50',
    },
    conditions: { plan: 'personal' },
  },
)

// [
//   { field: 'companyName', reason: 'business plan required', suggestedValue: undefined },
//   { field: 'companySize', reason: 'business plan required', suggestedValue: undefined },
// ]

Packages

Package Purpose
@umpire/core Pure logic engine with zero runtime dependencies
@umpire/react useUmpire() hook for React
@umpire/signals Signal adapter via SignalProtocol (Jotai, Preact, Alien Signals, TC39)
@umpire/store Generic store adapter β€” bring your own getState() + subscribe(next, prev)
@umpire/zustand Zustand adapter (satisfies the store contract natively)
@umpire/redux Redux / Redux Toolkit adapter
@umpire/tanstack-store TanStack Store adapter
@umpire/pinia Pinia adapter (Vue 3)
@umpire/vuex Vuex 4 adapter (Vue 3)
@umpire/zod Availability-aware Zod schemas β€” disabled fields produce no errors
@umpire/reads Derived read tables and read-backed rule bridges
@umpire/testing Invariant probes for rule configurations
@umpire/devtools In-app inspector panel β€” scorecard, traces, foul log, graph view

Why Umpire?

  • Pure logic, zero dependencies.
  • Declarative rules: requires, disables, enabledWhen, fairWhen, oneOf.
  • Recommendations, not mutations: play() suggests resets, you decide when to apply them.
  • Adapters for React, Zustand, Redux, TanStack Store, Pinia, Vuex, and signals.
  • Debuggable: challenge() traces why any field was ruled out, @umpire/devtools surfaces it visually.

Install

npm install @umpire/core

Published packages do not require Bun to consume.

Contributing

Local repo work expects:

  • Node 24+
  • Yarn 4
  • Bun 1.2+

yarn test and yarn test:coverage use Bun under the hood, so those commands will fail if Bun is not installed.

Docs

Full docs, concepts, and examples live at https://sdougbrown.github.io/umpire/

Droid-Friendly

Each published package ships a tight AGENTS.md file for cross-agent discoverability, with .claude/rules/* included as a Claude-specific compatibility surface. In this repo, AGENTS.md is canonical and CLAUDE.md plus .cursor/rules/ are compatibility symlinks.

Status

Alpha.

License

MIT

About

πŸ›‚ Reactive derived state for forms and apps with interdependent options. Get your app to play by the rules.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors