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

@gobing-ai/ts-dual-workflow-engine

v0.2.8

Published

@gobing-ai/ts-dual-workflow-engine — State-machine and transition-flow workflow runtime.

Readme

@gobing-ai/ts-dual-workflow-engine

State-machine and transition-flow workflow runtime with pluggable action runners, guard runners, and memory or database persistence.

What It Provides

ts-dual-workflow-engine runs declarative workflows in two execution modes:

| Mode | Use When | |------|----------| | state-machine | A run owns one current state and chooses the next state by evaluating ordered transition guards | | transition-flow | A run moves through nodes and edges in a DAG-like flow, executing node actions as it advances |

The package exposes:

| Export | Purpose | |--------|---------| | WorkflowService | High-level loader and runner for both workflow kinds | | StateMachineDriver | Direct state-machine execution | | TransitionFlowDriver | Direct transition-flow execution | | WorkflowEngineHost | Registry for action runners and guard runners | | MemoryWorkflowPersistenceAdapter | In-memory persistence for tests and short-lived runs | | DbWorkflowPersistenceAdapter | DB-backed persistence over @gobing-ai/ts-db | | loadWorkflowDef() / loadWorkflowDefFromText() | YAML workflow loading and validation | | applyWorkflowEngineSchema() | Installs the package-owned DB schema |

Installation

bun add @gobing-ai/ts-dual-workflow-engine @gobing-ai/ts-db

Use @gobing-ai/ts-db only when you need durable workflow history. Memory persistence has no database requirement.

State Machine Example

import {
    MemoryWorkflowPersistenceAdapter,
    StateMachineDriver,
    WorkflowEngineHost,
    type ActionRunner,
} from '@gobing-ai/ts-dual-workflow-engine';

const captureAction: ActionRunner = {
    kind: 'capture',
    async execute(options) {
        console.log(options.message);
        return { ok: true };
    },
};

const host = new WorkflowEngineHost()
    .registerAction(captureAction)
    .registerGuard({ kind: 'always', evaluate: async () => true });

const driver = new StateMachineDriver({
    host,
    persistence: new MemoryWorkflowPersistenceAdapter(),
});

const result = await driver.run(
    {
        name: 'approval',
        initialState: 'draft',
        terminalStates: ['done'],
        vars: { message: 'approved' },
        states: [
            { id: 'draft', onEnter: [{ kind: 'capture', options: { message: '${vars.message}' } }] },
            { id: 'done' },
        ],
        transitions: [{ from: 'draft', to: 'done', guard: { kind: 'always' } }],
    },
    { runId: 'approval-1' },
);

console.log(result.status, result.finalState);

The driver persists each state snapshot, phase update, transition, and final run status through the configured persistence adapter.

Transition Flow Example

import {
    createDefaultWorkflowEngineHost,
    MemoryWorkflowPersistenceAdapter,
    WorkflowService,
} from '@gobing-ai/ts-dual-workflow-engine';

const service = new WorkflowService(
    createDefaultWorkflowEngineHost(),
    new MemoryWorkflowPersistenceAdapter(),
);

const result = await service.run({
    kind: 'transition-flow',
    name: 'linear-flow',
    initialNode: 'start',
    terminalNodes: ['done'],
    nodes: [
        { id: 'start', action: { kind: 'note', options: { message: 'started' } } },
        { id: 'done' },
    ],
    edges: [{ from: 'start', to: 'done' }],
});

console.log(result);

The default host includes built-in note and shell action runners plus an always guard. For production systems, register domain-specific runners and keep shell execution explicit.

Load Workflows from YAML

import { loadWorkflowDef, WorkflowService } from '@gobing-ai/ts-dual-workflow-engine';

const workflow = await loadWorkflowDef('./workflows/approval.yaml');
await service.run(workflow, { runId: 'approval-1' });

loadWorkflowDef(path) reads YAML or JSON from disk. File loads honor a top-level $schema ref by default, then validate the internal structural schema and semantic references before returning a WorkflowDef. The $schema value resolves from the bundled package schema (shipped under node_modules/@gobing-ai/ts-dual-workflow-engine/schemas/) — no network access; quote the value, since YAML treats a leading @ as reserved. Relative paths and (opt-in) remote URLs also work; see @gobing-ai/ts-runtimeStructured config. loadWorkflowDefFromText(text, source) handles inline definitions with internal validation only.

State-machine YAML

kind: state-machine is optional because state-machine is the default shape, but including it makes the file easier to scan.

# workflows/approval.yaml
$schema: "@gobing-ai/ts-dual-workflow-engine/schemas/state-machine-workflow.schema.json"
kind: state-machine
name: approval
initialState: draft
terminalStates: [done]
vars:
  reviewer: robin
env:
  allow: [APP_ENV]
states:
  - id: draft
    onEnter:
      - kind: note
        options:
          message: "review requested by ${vars.reviewer} in ${env.APP_ENV}"
  - id: approved
    onEnter:
      - kind: note
        options:
          message: approved
  - id: done
transitions:
  - from: draft
    to: approved
    guard:
      kind: always
  - from: approved
    to: done
import {
    createDefaultWorkflowEngineHost,
    loadWorkflowDef,
    MemoryWorkflowPersistenceAdapter,
    WorkflowService,
} from '@gobing-ai/ts-dual-workflow-engine';

const service = new WorkflowService(
    createDefaultWorkflowEngineHost(),
    new MemoryWorkflowPersistenceAdapter(),
);

const workflow = await loadWorkflowDef('./workflows/approval.yaml');
const result = await service.run(workflow, {
    runId: 'approval-1',
    env: { APP_ENV: 'development' },
});

Transition-flow YAML

Transition-flow definitions must declare kind: transition-flow.

# workflows/import-file.yaml
$schema: "@gobing-ai/ts-dual-workflow-engine/schemas/transition-flow-workflow.schema.json"
kind: transition-flow
name: import-file
initialNode: read
terminalNodes: [done]
vars:
  file: events.jsonl
nodes:
  - id: read
    type: action
    action:
      kind: note
      options:
        message: "reading ${vars.file}"
  - id: validate
    type: gate
  - id: done
edges:
  - from: read
    to: validate
  - from: validate
    to: done
    condition:
      kind: always
const workflow = await loadWorkflowDef('./workflows/import-file.yaml');
const result = await service.run(workflow, {
    runId: 'import-1',
    vars: { file: 'override.jsonl' },
});

validateWorkflowDef() is available when the caller already has an object and only needs validation.

Variables and Environment

Actions receive resolved template values. The engine supports:

| Template | Source | |----------|--------| | ${vars.name} | Workflow vars merged with run vars | | ${env.NAME} | Environment values explicitly allowed by workflow config | | ${runId} | Current run ID | | ${workflow} | Workflow name | | ${state} | Current state or node ID |

await service.run(workflow, {
    vars: { file: 'events.jsonl' },
    env: { API_TOKEN: process.env.API_TOKEN },
    metadata: { requestedBy: 'scheduler' },
});

The workflow definition controls which environment names are visible through env.allow.

DB Persistence

import { createDbAdapter } from '@gobing-ai/ts-db';
import {
    applyWorkflowEngineSchema,
    createDefaultWorkflowEngineHost,
    DbWorkflowPersistenceAdapter,
    WorkflowService,
} from '@gobing-ai/ts-dual-workflow-engine';

const db = await createDbAdapter({ driver: 'bun-sqlite', url: './workflow.db' });
await applyWorkflowEngineSchema(db);

const service = new WorkflowService(
    createDefaultWorkflowEngineHost(),
    new DbWorkflowPersistenceAdapter(db),
);

Use service.listRuns() to read persisted run records. The adapter stores run status, phase snapshots, state snapshots, and transitions.

Error Handling

Validation failures throw WorkflowValidationError. Runtime finite-state-machine errors throw FSMError. Run failures caused by actions or guards are returned as WorkflowRunResult with status: 'failed', preserving the run record.

Boundary Notes

  • The engine executes workflows; it does not provide a scheduler. Use @gobing-ai/ts-infra scheduler or an external cron trigger to start runs.
  • Persistence is adapter-based. Downstream apps own DB lifecycle and migration ordering.
  • Action and guard runners are the extension points. Keep domain behavior there, not in workflow parsing.