Skip to content

processengine/flows

Repository files navigation

@processengine/flows

npm version CI license node Russian specification

@processengine/flows is the flow lifecycle runtime for the ProcessEngine family.

It validates declarative flows, prepares a transport-safe runtime artifact, plans the current normalized step, and applies state transitions through a strict process-state model.

It is designed for long-lived application processes such as onboarding, request review, fulfillment, and other business scenarios where the team needs:

  • a strict boundary between flow semantics and host infrastructure
  • explicit PROCESS, EFFECT, WAIT, and TERMINAL steps
  • compile-time validation before runtime
  • JSON-safe prepared artifacts and runtime contracts
  • a process state that stays readable in raw JSON

It is not a BPMN engine, not a workflow platform, not a transport layer, and not a host-side orchestration framework.

Specifications:

Place In ProcessEngine

@processengine/mappings   -> data normalization
@processengine/rules      -> business checks
@processengine/decisions  -> decision outcomes
@processengine/flows      -> process structure and lifecycle semantics

flows owns process structure and process-state transitions. It does not execute business artefacts itself and it does not own host infrastructure.

Installation

npm install @processengine/flows

Public API

import {
  validateFlow,
  prepareFlow,
  createProcessState,
  plan,
  reduce,
  apply,
  resume,
  createFlowTrace,
  formatFlowDiagnostics,
  formatFlowRuntimeError,
  formatFlowTrace,
} from "@processengine/flows";

What each function does:

  • validateFlow(flow, options) validates DSL and returns diagnostics.
  • prepareFlow(flow, options) returns a separate prepared artifact.
  • createProcessState({ flow, processId, input, meta }) creates the canonical initial state bound to flowId and flowVersion.
  • plan(preparedFlow, state) returns the current normalized step.
  • reduce(step, state, output) applies a PROCESS result.
  • apply(preparedFlow, state, stepId, effectResult) applies an EFFECT result.
  • resume(preparedFlow, state, stepId, waitResult) resumes a WAIT step.
  • createFlowTrace(state, mode?) materializes canonical basic or verbose trace views from ProcessState.
  • formatFlowDiagnostics(...), formatFlowRuntimeError(...), and formatFlowTrace(...) format public diagnostics, runtime errors, and trace output for logs or CLI output.

Quick Start

1. Define a flow

import type { FlowDefinition } from "@processengine/flows";

const flow: FlowDefinition = {
  id: "request.processing",
  version: "2.0.0",
  entryStepId: "validate_request",
  steps: [
    {
      id: "validate_request",
      type: "PROCESS",
      artefactId: "process.validate",
      inputRef: "$.context.input",
      outputRef: "$.context.facts.shouldContinue",
      nextStepId: "route_after_validation",
    },
    {
      id: "route_after_validation",
      type: "PROCESS",
      subtype: "ROUTE",
      factRef: "$.context.facts.shouldContinue.ok",
      cases: {
        true: "send_command",
        false: "complete_fail",
      },
      defaultNextStepId: "complete_fail",
    },
    {
      id: "send_command",
      type: "EFFECT",
      subtype: "COMMAND",
      artefactId: "effect.remote.submit",
      inputRef: "$.context.input",
      nextStepId: "wait_for_response",
      onErrorStepId: "complete_fail",
    },
    {
      id: "wait_for_response",
      type: "WAIT",
      sourceStepId: "send_command",
      nextStepId: "complete_success",
      onErrorStepId: "complete_fail",
      onTimeoutStepId: "complete_fail",
    },
    {
      id: "complete_success",
      type: "TERMINAL",
      subtype: "COMPLETE",
      result: {
        status: "COMPLETE",
        outcome: "REQUEST_COMPLETED",
      },
    },
    {
      id: "complete_fail",
      type: "TERMINAL",
      subtype: "FAIL",
      result: {
        status: "FAIL",
        outcome: "REQUEST_REJECTED",
      },
    },
  ],
};

2. Validate and prepare

import { prepareFlow, validateFlow } from "@processengine/flows";

const validation = validateFlow(flow, {
  registry: {
    hasArtefact: () => true,
    hasDecisionSet: () => true,
  },
  strict: true,
});

if (!validation.ok) {
  console.error(validation.diagnostics);
  process.exit(1);
}

const preparedFlow = prepareFlow(flow, {
  registry: {
    hasArtefact: () => true,
    hasDecisionSet: () => true,
  },
  strict: true,
});

3. Create state

import { createProcessState } from "@processengine/flows";

let state = createProcessState({
  flow: preparedFlow,
  processId: "request-001",
  trace: "basic",
  input: {
    requestId: "REQ-1",
    entityId: "ENTITY-1",
    payload: {
      amount: 100,
    },
  },
});

4. Run the lifecycle

import { apply, plan, reduce, resume } from "@processengine/flows";

let step = plan(preparedFlow, state);

if (step.type === "PROCESS") {
  const output = { ok: true };
  state = reduce(step, state, output);
}

step = plan(preparedFlow, state);

if (step.type === "PROCESS" && step.subtype === "ROUTE") {
  state = reduce(step, state, null);
}

step = plan(preparedFlow, state);

if (step.type === "EFFECT") {
  state = apply(preparedFlow, state, step.id, {
    requestId: "req-1",
    correlationKey: "request-001",
    result: null,
    error: null,
    errorCode: null,
  });
}

step = plan(preparedFlow, state);

if (step.type === "WAIT") {
  state = resume(preparedFlow, state, step.id, {
    requestId: "req-2",
    result: { status: "SUCCESS" },
    error: null,
    errorCode: null,
  });
}

step = plan(preparedFlow, state);

if (step.type === "TERMINAL") {
  console.log(step.result);
}

5. Format diagnostics and trace

import {
  createFlowTrace,
  formatFlowDiagnostics,
  formatFlowTrace,
} from "@processengine/flows";

const validation = validateFlow(flow, { strict: false });
console.log(formatFlowDiagnostics(validation.diagnostics));

const trace = createFlowTrace(state);
console.log(formatFlowTrace(trace));

DSL Summary

Supported step families:

  • PROCESS
  • PROCESS/ROUTE
  • PROCESS/SWITCH
  • EFFECT/COMMAND
  • EFFECT/CALL
  • WAIT
  • TERMINAL/COMPLETE
  • TERMINAL/FAIL

PROCESS without a subtype is a first-class base step family. subtype is used only for specialized PROCESS variants (ROUTE, SWITCH), EFFECT, and TERMINAL.

Boundary Rules

  • flows owns DSL, validation, prepared artifact, normalized planning, and process-state transitions.
  • process executes only normalized PROCESS steps and does not decide lifecycle transitions.
  • Host owns artefact registries, external I/O, persistence, delivery mechanics, correlation, retries, and infrastructure loops.
  • Host must restore the prepared flow by (state.flowId, state.flowVersion) for long-lived processes.
  • COMMAND and WAIT remain separate steps in the model.
  • plan(...) returns a self-contained normalized step, so Host does not resolve refs from process state.

Schema Export

JSON Schema is available via the package subpath:

import schema from "@processengine/flows/schema";

About

Declarative flow DSL, compiler and runtime model for application processes

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors