Skip to content

Workflows

Workflows live in <entity>.workflows.ts and convert domain evaluation into typed UI interaction state. No React, no DB access.

  • direct — proceed without an entity-mandated extra interaction step
  • confirmation — user must confirm
  • selection — user must choose between options
  • blocked — action not allowed
  • no_changes — nothing effective to do
  • localized interaction text
  • severity metadata
  • typed next-step contracts for the UI
  • mapping from domain truth to interaction state

They do not execute writes and they do not read query state.

type EntityDeleteResolution =
| DirectResolution<"delete">
| ConfirmationResolution<"delete">
| BlockedResolution
| SelectionResolution<...>;
function resolveEntityDelete(input): EntityDeleteResolution

Use delete workflows for:

  • warning-only confirmations
  • blocked destructive actions
  • delete-to-deactivate transformations
  • selection-based delete modes

Return a full edit contract, not only a boolean:

interface EntityEditResolution {
canEdit: boolean;
editability: FieldEditability<...>;
blockedReason?: LocalizedText;
dateLimits?: ...;
timeState?: ...;
}

This lets forms ask:

  • can the entity be edited at all?
  • which fields are editable?
  • why is a field or the whole record locked?
  • are there additional limits like date boundaries?

For cases where a submitted update may branch into multiple interaction paths:

  • blocked
  • no_changes
  • direct apply
  • user selection required

direct means the workflow does not require an extra interaction step. It does not mean “always execute immediately”. If the application wants a generic destructive confirm for all deletes, that should be a UI-level policy.

  • finance/single-cashflow for direct vs deactivate vs blocked delete
  • finance/recurring-cashflow for branching update resolution
  • work/time-entry for blocked delete and editability contract