Skip to content

Guards

Guards live in <entity>.guards.ts and own pure domain evaluation. No React, no DB access, no side effects.

Guards compute domain truth from plain input values.

  • operation guards such as canDelete<Entity>(), canUpdate<Entity>(), canSelect<Entity>()
  • field editability via get<Entity>Editability()
  • richer derived state such as time state, date limits, delete modes, propagation categories, or update-option inputs

Each entity should prefer operation-specific guard input types:

export interface EntityDeleteGuardInput { ... }
export interface EntityEditGuardInput { ... }
export interface EntityUpdateGuardInput { ... }

Simple entities may use empty input types:

type EntityDeleteGuardInput = Record<string, never>;

That still preserves the same public contract as complex entities.

If multiple operations depend on the same facts, prefer one consolidated evaluator to prevent rule drift:

evaluateEntity(input) // returns guards, editability, and derived state

This is the best pattern when delete, update, select, and editability all depend on the same underlying state.

  • guards should return domain truth, not UI instructions
  • guards should not localize dialog copy
  • guards should not read query state directly
  • guards should not know about React components or mutation adapters
  • work/time-entry uses a consolidated evaluateWorkTimeEntry(...)
  • work/project exposes both delete warnings and field editability
  • finance/recurring-cashflow exposes richer derived state for update decisions