npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@objectql/plugin-workflow

v4.2.2

Published

State machine workflow engine plugin for ObjectQL - Full XState-level state machine executor with guards, actions, and compound states

Readme

@objectql/plugin-workflow

State machine workflow engine plugin for ObjectQL. Provides full XState-level state machine execution with guards, actions, and compound states.

Overview

The Workflow Plugin implements a powerful state machine engine that manages complex business workflows through declarative configuration. It operates at the Hook/Validation layer, intercepting state field changes via beforeUpdate hooks to evaluate guards, execute actions, and enforce state transition rules.

Key Features

  • XState-Compatible: Full support for XState state machine patterns
  • Guard Conditions: Declarative conditions that control transitions
  • Entry/Exit Actions: Execute side effects during state transitions
  • Compound States: Hierarchical state nesting with automatic resolution
  • Audit Trail: Optional persistence of all state transitions
  • Type-Safe: Full TypeScript support with protocol-derived types
  • Zero SQL Impact: Operates at hook layer, no changes to query generation

Installation

pnpm add @objectql/plugin-workflow

Quick Start

1. Define State Machine in Object Metadata

# project.object.yml
name: project
fields:
  status:
    type: select
    options: [draft, active, done]
    default: draft

stateMachine:
  initial: draft
  states:
    draft:
      on:
        submit:
          target: active
    active:
      on:
        complete:
          target: done
    done:
      type: final

2. Install the Plugin

import { WorkflowPlugin } from '@objectql/plugin-workflow';
import { ObjectStackKernel } from '@objectstack/runtime';

const kernel = new ObjectStackKernel([
  new WorkflowPlugin({
    enableAuditTrail: true,
  }),
  // ... other plugins
]);

await kernel.start();

3. State Transitions Work Automatically

// Valid transition - allowed
await api.update('project', 'p1', { status: 'active' });
// ✅ Success

// Invalid transition - denied
await api.update('project', 'p1', { status: 'done' });
// ❌ ObjectQLError: TRANSITION_DENIED

Features

Guards (Conditions)

Control which transitions are allowed:

stateMachine:
  states:
    draft:
      on:
        submit:
          target: pending_approval
          cond:
            field: complete
            operator: equals
            value: true

Built-in guards:

  • hasRole:roleName - User has specific role
  • hasPermission:permission - User has permission
  • isOwner - User owns the record
  • isCreator - User created the record
  • field:expression - Field-based conditions

Actions

Execute side effects during transitions:

stateMachine:
  states:
    draft:
      exit:
        - timestamp:submitted_at
    active:
      entry:
        - notifyStakeholders
        - setField:active_at=$now

Built-in actions:

  • setField:field=value - Update field value
  • increment:field - Increment numeric field
  • timestamp:field - Set timestamp
  • log:message - Console log

Compound States

Hierarchical state nesting:

stateMachine:
  initial: editing
  states:
    editing:
      initial: draft
      states:
        draft: {}
        review: {}
      on:
        publish:
          target: published
    published:
      type: final

Audit Trail

Track all state transitions:

const plugin = new WorkflowPlugin({ enableAuditTrail: true });

// Query audit trail
const trail = plugin.getAuditTrail({
  objectName: 'project',
  recordId: 'p123',
});

console.log(trail);
// [
//   {
//     id: '...',
//     objectName: 'project',
//     recordId: 'p123',
//     currentState: 'active',
//     previousState: 'draft',
//     timestamp: '2026-02-07T...',
//     actionsExecuted: ['onExitDraft', 'onEnterActive'],
//   }
// ]

Configuration

WorkflowPluginConfig

interface WorkflowPluginConfig {
  /** Enable audit trail persistence. Default: false */
  enableAuditTrail?: boolean;

  /** Custom guard resolver for external guards */
  guardResolver?: (guardRef: string, context: ExecutionContext) => Promise<boolean>;

  /** Custom action executor for external actions */
  actionExecutor?: (actionRef: string, context: ExecutionContext) => Promise<void>;
}

Custom Guard Resolver

const plugin = new WorkflowPlugin({
  guardResolver: async (guardRef, context) => {
    if (guardRef === 'budgetApproved') {
      const budget = await fetchBudgetStatus(context.record.id);
      return budget.approved;
    }
    return false;
  },
});

Custom Action Executor

const plugin = new WorkflowPlugin({
  actionExecutor: async (actionRef, context) => {
    if (actionRef === 'notifyApprover') {
      await sendEmail({
        to: context.record.approver_email,
        subject: 'Approval Required',
        body: `Project ${context.record.name} needs your approval`,
      });
    }
  },
});

API Reference

StateMachineEngine

import { StateMachineEngine } from '@objectql/plugin-workflow';

const engine = new StateMachineEngine(config, guardEvaluator, actionExecutor);

const result = await engine.transition('draft', 'active', context);

if (result.allowed) {
  console.log('Transition allowed');
} else {
  console.error('Denied:', result.error);
}

GuardEvaluator

import { GuardEvaluator } from '@objectql/plugin-workflow';

const evaluator = new GuardEvaluator(customResolver);

const result = await evaluator.evaluate(
  { field: 'approved', operator: 'equals', value: true },
  context
);

console.log(result.passed); // true or false

ActionExecutor

import { ActionExecutor } from '@objectql/plugin-workflow';

const executor = new ActionExecutor(customExecutor);

await executor.execute('notifyApprover', context);
await executor.executeMultiple(['action1', 'action2'], context);

Architecture

┌──────────────────────────────┐
│  plugin-workflow             │  ← beforeUpdate hook
│  (State Machine Executor)    │
├──────────────────────────────┤
│  plugin-validator            │  ← field/cross-field validation
├──────────────────────────────┤
│  QueryService → QueryAST     │  ← Core: query building
├──────────────────────────────┤
│  Driver → Knex → SQL         │  ← Driver: SQL generation (UNTOUCHED)
└──────────────────────────────┘

The Workflow Plugin:

  1. Registers a beforeUpdate hook
  2. Detects state field changes in the update payload
  3. Evaluates guards against the state machine configuration
  4. Executes entry/exit/transition actions
  5. Either allows the update to proceed or throws ObjectQLError

Examples

Approval Workflow

name: expense_report
stateMachine:
  initial: draft
  states:
    draft:
      on:
        submit:
          target: pending
          cond:
            field: amount
            operator: greater_than
            value: 0
    
    pending:
      entry:
        - notifyApprover
      on:
        approve:
          target: approved
          cond: hasRole:approver
          actions:
            - setField:approved_by=$user.id
            - timestamp:approved_at
        reject:
          target: rejected
    
    approved:
      type: final
    
    rejected:
      on:
        resubmit:
          target: draft

Project Lifecycle

name: project
stateMachine:
  initial: planning
  states:
    planning:
      on:
        start:
          target: active
          cond:
            all_of:
              - field: team_assigned
                operator: equals
                value: true
              - field: budget_approved
                operator: equals
                value: true
    
    active:
      entry:
        - setField:started_at=$now
        - notifyTeam
      on:
        pause:
          target: on_hold
        complete:
          target: completed
    
    on_hold:
      on:
        resume:
          target: active
    
    completed:
      type: final

Testing

import { describe, it, expect } from 'vitest';
import { WorkflowPlugin } from '@objectql/plugin-workflow';

describe('Project Workflow', () => {
  it('should allow valid transitions', async () => {
    const plugin = new WorkflowPlugin({ enableAuditTrail: true });
    
    // Test setup...
    
    const result = await engine.transition('draft', 'active', context);
    expect(result.allowed).toBe(true);
  });
});

Run tests:

pnpm test

License

MIT

Contributing

See the main ObjectQL Contributing Guide.

Documentation

Full documentation: ObjectQL Workflow Engine