ClinkClang
clinkclang-docs
Workflow Tracker
Last updated: 2/17/2025

The Workflow Tracker is a module designed to help build and track your workflows seamlessly through a state machine and type-safe step definitions.

Installation

# Add the workflow package
pnpm dlx clinkclang@latest add workflows

# Add the workflow-examples package
pnpm dlx clinkclang@latest add workflows-examples

Creating your workflow

Creating a new Workflow instance

const testWorkflow = new Workflow({
	workflowName: 'test-workflow',
	initSchema: z.object({ initValue: z.number() })
});

The initSchema field allows you to define an input schema that will help safely execute the workflow with a chosen input value. We’ll talk more about this down below!

Defining a Step

const testOne = new Step({
	stepId: 'sayHello',
	execute: async () => {
		console.log("Hello!");
	}
});

The most basic parts of a step are its stepId and execute function. Optionally, the function defined within the Step may also return a value that can later be accessible by other steps!

Building your Workflow

You can chain your steps together like so:

const workflowInstance = testWorkflow
	.do(testOne)
	.then(testTwo)
	.then(testThree)
	.commit()
	.createInstance();

The above returns a function that allows you to start the Workflow runner. This function returns a Promise that contains the results of your Workflow. We can access it through the .then() method as follows:

workflowInstance.run().then(() => {
	console.log(testWorkflow.workflowContext);
});

Example console output:

{
  workflowName: 'test-workflow',
  results: {
    addOneToNumber: { stepId: 'addOneToNumber', output: 6, status: 'success' },
    doubleNumber: { stepId: 'doubleNumber', output: 12, status: 'success' },
    subtractFiveFromNumber: { stepId: 'subtractFiveFromNumber', output: 7, status: 'success' }
  },
  initData: { initValue: 5 }
}

Playing with values

Accessing values from other steps

See the following step definition:

const testOne = new Step({
	stepId: 'returnOne',
	execute: async () => { return 1	}
});

If we wanted to access this value from other steps, we can use the WorkflowContext instance that will be automatically passed into the function by the Workflow runner!

const testTwo = new Step({
	stepId: 'printPrevResult',
	execute: async (ctx) => {
		if (ctx.results.returnOne?.status == ExecutionStatus.Success) {
			console.log(ctx.results.returnOne.output); // 1
		}
	}
});

Init Data

We mentioned above that you’re able to provide the context of your Workflow with some initialization data. Here’s how you can do it:

const testWorkflow = new Workflow({
	workflowName: 'test-workflow',
	initSchema: z.object({ initValue: z.number() })
});

The initSchema field of the WorkflowConfig you use to define the Workflow takes in an object that maps out the shape of your input values! For example, if you wanted to use the following object as your initialization values:

{
  initValue: 5,
  helloString: "hello!",
}

You can use the following initSchema to instantiate your Workflow:

const schema = z.object({
  initValue: z.number,
  helloString: z.string,
})

Within any Step, you can now access this value by accessing the initData field of the WorkflowContext, while being compiler-safe!

const testOne = new Step({
	stepId: 'test',
	execute: async ({ initData }) => { 
    	console.log(initData.initValue) // 5
      console.log(initData.hellostring) // "hello!"
  }
});