npm package: @processengine/flows
Description: Declarative flow DSL and process-state lifecycle runtime for application processes
Latest version: 2.2.0

# `@processengine/flows`

Declarative flow DSL and process-state runtime for the ProcessEngine family.

`flows` owns:
- Flow DSL validation and preparation
- Immutable `PreparedFlow`
- Step normalization via `plan(...)`
- Process-state transitions via `reduce(...)`, `apply(...)`, `resume(...)`
- Internal resolution of `PROCESS/ROUTE` and `PROCESS/SWITCH`

`flows` does not own:
- Runtime module execution
- Host transport, persistence, retries, or timers
- External side-effect execution
- `requestId` generation

## Public API

```ts
validateFlow(flow, options?)  -> ValidationResult
prepareFlow(flow, options?)   -> PreparedFlow

createProcessState(params)                         -> ProcessState
plan(preparedFlow, state)                          -> NormalizedStep
reduce(step, state, output)                        -> ProcessState
apply(preparedFlow, state, stepId, effectResult)   -> ProcessState
resume(preparedFlow, state, stepId, waitResult)    -> ProcessState
```

`createProcessState` is the only canonical way to produce a valid initial `ProcessState`. Host must not construct `ProcessState` manually.

## Canonical DSL

```ts
import type { FlowDefinition } from '@processengine/flows';

const flow: FlowDefinition = {
  id: 'request.processing',
  version: '2026-04-09',
  entryStepId: 'validate_input',
  steps: {
    validate_input: {
      id: 'validate_input',
      type: 'PROCESS',
      subtype: 'RULES',
      artefactId: 'process.validate',
      inputRef: '$.context.input',
      outputRef: '$.context.checks.validation',
      nextStepId: 'map_validation',
    },
    map_validation: {
      id: 'map_validation',
      type: 'PROCESS',
      subtype: 'MAPPINGS',
      artefactId: 'process.mapValidation',
      inputRef: '$.context.checks.validation',
      outputRef: '$.context.facts.validation',
      nextStepId: 'route_validation',
    },
    route_validation: {
      id: 'route_validation',
      type: 'PROCESS',
      subtype: 'ROUTE',
      factRef: '$.context.facts.validation.ready',
      cases: {
        true: 'send_command',
        false: 'finish_fail',
      },
      defaultNextStepId: 'finish_fail',
    },
    send_command: {
      id: 'send_command',
      type: 'EFFECT',
      subtype: 'COMMAND',
      operationId: 'remote.submit',
      inputRef: '$.context.input',
      nextStepId: 'wait_response',
      onErrorStepId: 'finish_fail',
    },
    wait_response: {
      id: 'wait_response',
      type: 'WAIT',
      subtype: 'MESSAGE',
      sourceStepId: 'send_command',
      nextStepId: 'finish_complete',
      onErrorStepId: 'finish_fail',
      onTimeoutStepId: 'finish_timeout',
    },
    finish_complete: {
      id: 'finish_complete',
      type: 'TERMINAL',
      subtype: 'COMPLETE',
      result: { status: 'COMPLETE', outcome: 'REQUEST_COMPLETED' },
    },
    finish_timeout: {
      id: 'finish_timeout',
      type: 'TERMINAL',
      subtype: 'FAIL',
      result: { status: 'FAIL', outcome: 'REQUEST_TIMEOUT' },
    },
    finish_fail: {
      id: 'finish_fail',
      type: 'TERMINAL',
      subtype: 'FAIL',
      result: { status: 'FAIL', outcome: 'REQUEST_FAILED' },
    },
  },
};
```

### Supported step families

- `PROCESS/RULES`
- `PROCESS/MAPPINGS`
- `PROCESS/DECISIONS`
- `PROCESS/ROUTE`
- `PROCESS/SWITCH`
- `EFFECT/COMMAND`
- `EFFECT/CALL`
- `WAIT/MESSAGE`
- `TERMINAL/COMPLETE`
- `TERMINAL/FAIL`

`steps` is an object map. The key must match `step.id`.

## Quick start

```ts
import {
  apply,
  createProcessState,
  formatValidationIssues,
  plan,
  prepareFlow,
  reduce,
  resume,
  validateFlow,
} from '@processengine/flows';

const validation = validateFlow(flow);
if (!validation.isValid) {
  throw new Error(formatValidationIssues(validation.errors));
}

const preparedFlow = prepareFlow(flow);

let state = createProcessState({
  flow: preparedFlow,
  processId: 'proc_001',
  input: {
    requestId: 'REQ-001',
    payload: { amount: 100 },
  },
});

let step = plan(preparedFlow, state);
if (step.type !== 'PROCESS' || step.subtype !== 'RULES') throw new Error('Expected RULES');
state = reduce(step, state, { ok: true, errors: [] });

step = plan(preparedFlow, state);
if (step.type !== 'PROCESS' || step.subtype !== 'MAPPINGS') throw new Error('Expected MAPPINGS');
state = reduce(step, state, { ready: true });

step = plan(preparedFlow, state);
if (step.type !== 'PROCESS' || step.subtype !== 'ROUTE') throw new Error('Expected ROUTE');
state = reduce(step, state, null);

step = plan(preparedFlow, state);
if (step.type !== 'EFFECT') throw new Error('Expected EFFECT');
state = apply(preparedFlow, state, step.id, {
  requestId: 'req-001',
  result: { accepted: true },
  error: null,
  errorCode: null,
});

step = plan(preparedFlow, state);
if (step.type !== 'WAIT') throw new Error('Expected WAIT');
state = resume(preparedFlow, state, step.id, {
  requestId: 'req-001',
  result: { delivered: true },
  error: null,
  errorCode: null,
});

if (state.status !== 'COMPLETE') throw new Error('Expected terminal completion');
console.log(state.result);
```

The quick start intentionally does not call `plan(...)` after terminal completion. Once `state.status` becomes `COMPLETE` or `FAIL`, calling runtime methods again is a runtime violation.

## Validation and errors

`validateFlow(...)` returns the canonical validation result:

```ts
type ValidationResult = {
  isValid: boolean;
  errors: ValidationIssue[];
  warnings: ValidationIssue[];
};
```

- `validateFlow(...)` never throws for ordinary DSL errors
- `prepareFlow(...)` throws `XCompileError`
- `plan(...)`, `reduce(...)`, `apply(...)`, `resume(...)` throw `XRuntimeError` for runtime contract violations

## Runtime contracts

- `ProcessStatus` is `ACTIVE | WAITING | COMPLETE | FAIL`
- EFFECT runtime contract uses `operationId`
- External correlation contract uses `requestId` only
- Mixed `result + error` in `effectResult` or `waitResult` is invalid
- Missing runtime path resolution is invalid
- `WAIT.startedAt` is preserved on `resume(...)`

## Docs

- [SPEC.md](./SPEC.md)
- [SPEC_RU.md](./SPEC_RU.md)
- [COMPATIBILITY.md](./COMPATIBILITY.md)
- [CHANGELOG.md](./CHANGELOG.md)
- [Migration guide](./docs/MIGRATION_GUIDE.md)
- [Persistence guide](./docs/INTEGRATION_PERSISTENCE.md)
- [Scheduler guide](./docs/SCHEDULER_GUIDE.md)
- [Benchmarks](./docs/BENCHMARKS.md)
- [LICENSE](./LICENSE)