Updating Entities with Step Data

Guides: Evolving Workflow Data Between Steps (onCompleteMappers)

Workflows aren't static; they process information. As a workflow moves from one step to the next, data gathered in an earlier step often needs to be available or acted upon in a later step. For example, a client's name entered in Step 1 might need to appear on a document signed by a manager in Step 2.

Some systems handle this by simply passing raw field values directly between steps. SignStack uses a more robust, data-first approach. Your workflow revolves around clean, structured Entities (like ClientInfo or DealInfo). Information gathered from participants within a specific Step (as fields) is used to formally update or evolve these central Entities. Subsequent steps then work with this consistent, up-to-date Entity data.

Think of it like updating a customer's official record in your database. When a customer service agent talks to the customer (completes a step) and gets a new phone number (the field input), they don't just pass a sticky note to the next agent. They use a formal process (onCompleteMapper) to update the central customer record (the Entity). Subsequent agents then work from that updated, official record.

Sometimes, this data evolution is simple (copying a name from a field to an entity). Other times, it can be more complex, requiring calculations or logic that uses multiple inputs – perhaps combining data from several fields submitted in the step and data from existing Entities to calculate a new value or status.

This guide explains how SignStack uses onCompleteMappers to handle this crucial data evolution step-by-step, ensuring your workflow always operates on consistent and reconciled information.

What are onCompleteMappers?

onCompleteMappers are the mechanism for evolving the EntityContext (the collection of all entities in your workflow). They are optional instructions you add to a BlueprintStep that tell the SignStack engine how to update the canonical entity data after that step successfully completes.

Think of an onCompleteMapper as the specific instruction used to update the central record after a task finishes. It's a JSONata expression whose job is to take the current state (entities) and the new information collected within the completed step (fields) and produce the next valid state of a specific Entity.

  • When they run: After a step successfully completes, before the next step begins.

  • Their Purpose: To update the canonical EntityContext based on fields from the completed step, the existing entities state, or simply the completion event itself.

  • The Engine: SignStack uses the powerful JSONata expression engine.

Defining onCompleteMappers

The onCompleteMappers property on a BlueprintStep is an array of objects. Each object defines the transformation for a single target entity.

Structure:

// Inside a BlueprintStep definition
onCompleteMappers?: StepDataMapper[];

// The StepDataMapper structure
interface StepDataMapper {
  targetEntityKey: string; // Key of the entity slot to update (e.g., "clientInfo")
  mapperExpression: string; // JSONata expression returning the NEW entity object
}

Blueprint Example:

// Inside a BlueprintStep object...
"onCompleteMappers": [
  {
    "targetEntityKey": "clientInfo", // Update the 'clientInfo' entity
    "mapperExpression": "$merge([entities.clientInfo, { 'lastContactDate': $now() }])"
  },
  {
    "targetEntityKey": "dealInfo", // Also update the 'dealInfo' entity
    "mapperExpression": "$merge([entities.dealInfo, { 'status': 'INFO_COLLECTED' }])"
  }
]

Mapper Input Context

Your mapperExpression has access to a rich context object, including:

  • entities: The current state of all entities in the EntityContext before this mapper runs.

  • fields: An object containing the data submitted by the participant in the just-completed step. This object is structured by the documentKey (from the Blueprint document definition) and then the fieldKey (from the Template field definition).

Example Context:

{
  "entities": {
    "clientInfo": { "name": "Old Name", "status": "Pending" },
    "dealInfo": { "value": 50000 }
  },
  "fields": {
    "clientFormDoc": { // documentKey
      "clientNameField": { // fieldKey
        "value": { "text": "Jane Doe" }
      },
      "clientEmailField": {
         "value": { "text": "jane@example.com" }
      }
    }
  }
}

Example 1: Simple Field-to-Entity Mapping

After a client fills out their name and email in a form step (clientIntakeStep), save that data to the clientInfo entity.

onCompleteMappers for clientIntakeStep:

"onCompleteMappers": [
  {
    "targetEntityKey": "clientInfo",
    "mapperExpression": "$merge([ entities.clientInfo, { 'name': fields.clientFormDoc.clientNameField.value.text, 'email': fields.clientFormDoc.clientEmailField.value.text, 'status': 'Data Received' } ])"
  }
]
  • Explanation: The $merge function takes the existing clientInfo entity and updates its name, email, and status properties using the values submitted in the corresponding fields from the clientFormDoc.

Example 2: Updating Status and Using Existing Data

After a manager reviews and approves a deal (managerApprovalStep) by checking an approval box, update the dealInfo status and record the approval date.

onCompleteMappers for managerApprovalStep:

"onCompleteMappers": [
  {
    "targetEntityKey": "dealInfo",
    // Assumes the manager checked a field with key 'managerApprovalCheckbox'
    "mapperExpression": "$merge([ entities.dealInfo, { 'status': fields.approvalDoc.managerApprovalCheckbox.value.checked ? 'Approved' : 'Rejected', 'approvalDate': $now() } ])"
  }
]
  • Explanation: This expression checks the boolean value of the managerApprovalCheckbox field. If checked, it sets the dealInfo status to 'Approved'; otherwise, it sets it to 'Rejected'. It also adds the current timestamp as the approvalDate.

Parallel Steps & Merging

When onCompleteMappers run within steps inside a PARALLEL container, they update their own isolated "branch" of the EntityContext. The onCompleteMapper on the parent PARALLEL container itself is then responsible for merging these branched states back together. (See the Advanced Topics guide on "Handling Parallel Data" for details).

Best Practices

  • Keep Mappers Focused: Each mapper object in the array should target and update only one Entity.

  • Use Functions for Complexity: If your transformation logic is complex or needs to be reused, encapsulate it within a Custom Function and call that function from your mapperExpression.

  • Validate Before Mapping: Use the step's onCompleteValidationExpression (a single expression that runs before all mappers) to ensure the fields data or entities state is valid before attempting the transformations.

onCompleteMappers are the core mechanism for making your SignStack workflows intelligent and reactive, allowing the process state to evolve based on participant actions and business rules defined within your Blueprint.

➡️ Next: Guides: Use Custom Functions in Blueprints