Defining Workflows
Before you can define workflows, you need to set up your project and install the necessary SDK.
While IdentityFlow doesn’t enforce a specific structure for your source code if you’re building workflows as part of a larger application, the engine typically discovers workflow definitions from a specific location at runtime. For standard deployments, the recommended structure aligns with the engine’s default configuration:
your-project-root/├── config/ # Engine configuration typically resides here│ ├── workflows/ # Recommended location for workflow definitions & related files│ │ ├── bindings/ # Bindings specific to these workflows│ │ │ └── graphql.ts│ │ ├── types/ # Optional custom types for these workflows│ │ │ └── custom-types.ts│ │ ├── simple-approval.workflow.ts│ │ ├── user-onboarding.flow.ts│ │ ├── package.json # Optional: If workflows have specific dependencies│ │ ├── tsconfig.json # Optional: If workflows need specific TS config│ │ └── ...│ └── identity-flow.conf # Example engine configuration file└── ... (other project files)config/workflows/: Placing your workflow definition.tsfiles here allows the engine to discover them automatically with minimal configuration. This is the recommended location for simpler deployments. The engine will compile/bundle these TypeScript files internally.- Related Files: You can also co-locate related files like
bindings, customtypes, and evenpackage.jsonortsconfig.jsonwithin this directory if your workflows form a somewhat self-contained unit or have specific dependencies/compilation needs separate from a main application. - File Naming: The engine often looks for files ending in
.workflow.ts,.flow.ts, etc. (configurable). Using clear names likeuser-onboarding.workflow.tsis good practice.
Placing workflows and their immediate dependencies in config/workflows/ often simplifies deployment as the engine is configured to look there by default.
Workflow Definition Discovery by the Engine
Section titled “Workflow Definition Discovery by the Engine”- Your workflow definition
.tsfiles (likesimple-flow.ts) need to be placed in a location where the IdentityFlow engine can discover them. - The engine compiles and bundles these TypeScript files internally when it first discovers them or when they change. You do not need a separate build step for your workflow files.
- Internal compilation uses Sucrase for fast TypeScript transformation.
- Support is included for modern features like dynamic imports (
import('./module')), JSON imports (import data from './data.json' assert { type: 'json' }), and WASM imports (import wasm from './module.wasm' assert { type: 'wasm' }). - The compiled/bundled output is stored internally by the engine (typically in the database) for efficient execution.
- A common and recommended approach for standard deployments is to place your workflow definition
.tsfiles within aworkflowssubdirectory relative to your engine’s configuration file (e.g.,config/workflows/). - The engine automatically searches for files matching patterns like
**/*.workflow.ts,**/*.flow.ts, etc., within the configured path(s).- Default Include Patterns: By default, the engine looks for files matching globs like
**/*.{request,approval,flow,workflow,wf}.?(m)[jt]sand**/workflows/*.?(m)[jt]s. - Default Exclude Patterns: Common patterns like
.d.tsfiles, test files (*.test.ts,*.spec.ts), configuration files,node_modules,dist,build, etc., are excluded by default.
- Default Include Patterns: By default, the engine looks for files matching globs like
- You can customize the search paths and include/exclude patterns in the engine’s configuration file.
- Refer to the IdentityFlow Engine Deployment and Configuration Guide (link TBD) for detailed options.
- Ensure
draft: falseis set in yourdefineWorkflowconfiguration for definitions ready for use.
The core package for defining workflows is @identity-flow/sdk. Install it in your project using your preferred package manager:
npm i @identity-flow/sdkpnpm add @identity-flow/sdkyarn add @identity-flow/sdkThis package provides the defineWorkflow function and the flow helper object you’ll use extensively.
Workflows are defined in TypeScript files using the defineWorkflow function exported by @identity-flow/sdk.
This function takes two arguments:
- Configuration Object: An object specifying metadata and default settings for the workflow.
- Execute Function: An asynchronous function (
async (flow) => { ... }) containing the actual logic of your workflow.
Let’s look at a minimal workflow definition:
import { defineWorkflow } from '@identity-flow/sdk';
export default defineWorkflow( { // Core identification name: '@carv/simple-flow', // Unique identifier for the workflow definition version: '1.0.0', // Semantic version for this definition
// Optional metadata (useful for UI and organization) label: 'Simple Flow', // Human-readable name description: 'A basic example workflow',
// Deployment setting draft: false, // Set to false to allow engine deployment }, // The main logic of the workflow async (flow) => { flow.log('Workflow instance started!');
// Your workflow steps will go here... const result = 'Hello from workflow!';
flow.log('Workflow instance finishing.'); return result; // The final result of the workflow instance },);Key Configuration Properties:
name: (Required) A unique string identifying this workflow definition (e.g.,@your-org/process-name).version: (Required) A version string (preferably SemVer like1.0.0) to track changes.label: (Optional) A user-friendly name displayed in UIs.description: (Optional) A brief explanation of the workflow’s purpose.draft: (Optional, defaults totrue) Set tofalseto indicate the workflow is ready for deployment/use by the engine. Draft workflows are typically ignored by deployment processes.releaseChannel: (Optional, defaults to'latest') Used to tag specific versions (e.g.,'beta','stable').schema: (Optional) A validation schema (e.g., from Valibot) for the inputparams. See the Validation section later.defaults: (Optional) Default options (like retry settings) for all activities in this workflow.meta: (Optional) Arbitrary JSON-serializable metadata to attach to the workflow definition. Read viaflow.definition.meta.
This definition exports a configuration object that the IdentityFlow engine can discover and register. The execute function is where the core logic resides, which we’ll explore next.
Writing Workflow Logic: The execute Function
Section titled “Writing Workflow Logic: The execute Function”The second argument to defineWorkflow is the execute function. This is where you implement the steps and logic of your process.
async (flow) => { // Your workflow logic goes here};The execute function receives a single argument, typically named flow, which is an object of type Flow (from @identity-flow/sdk). This object is your primary interface for interacting with the workflow engine and orchestrating activities.
Key properties and methods on the flow object include:
-
flow.params: Accesses the input parameters passed when the workflow instance was started. If aschemawas provided in the definition config,paramswill be the validated and typed output.async (flow) => {const { userId, orderId } = flow.params; // Assuming schema validated theseflow.log(`Processing order ${orderId} for user ${userId}`);}; -
flow.vars: A mutable object (Record<string, JSONValue>) used primarily to expose internal state externally (e.g., for UI display via the GraphQL API). This state is persisted when the workflow waits (e.g., duringsleep,dialog,request), allowing external systems to query the current status or progress.async (flow) => {// Example: Expose progress for UIflow.vars.progress = { currentStep: 'Processing Payment', percentComplete: 50 };// ... workflow pauses ...// Update exposed progressflow.vars.progress = { currentStep: 'Order Shipped', percentComplete: 100 };};Important Distinction: While
flow.varsis persisted, use regular function scope variables (const,let) for managing internal processing state betweenawaitpoints within a single execution run before the workflow pauses.flow.varsis best reserved for data you need to make visible outside the workflow execution itself. For robust state management across persisted steps, always rely on passing data through step inputs and outputs.Learn more about managing state effectively in Rule 3: Persist State Through Steps.
-
flow.instance: Provides read-only access to information about the current workflow instance (e.g.,flow.instance.id,flow.instance.createdAt,flow.instance.meta). -
flow.definition: Provides read-only access to the definition configuration this instance is running (e.g.,flow.definition.name,flow.definition.version). -
Logging Methods (
flow.log,flow.info,flow.warn,flow.error,flow.debug,flow.trace): Standard methods for logging messages during workflow execution. These logs are captured by the engine and associated with the workflow instance and specific step, aiding debugging. -
Activity Methods (
flow.do,flow.sleep,flow.dialog,flow.request,flow.start): These are the core methods for defining the actual steps and pauses in your workflow. They are covered in detail in the Workflow Activities guide. -
flow.use(): A method for lazy-loading and injecting dependencies (like API clients or services). Covered in the Dependencies & Logging guide. -
flow.assert(): Utility to assert a condition is truthy, throwing a retryable error if not. Details in Error Handling & Retries.
The value returned by the execute function becomes the final result of the workflow instance when it completes successfully. This result is recorded in the instance’s event history and can be retrieved via the GraphQL API.
async (flow) => { // ... logic ... const finalReport = { processedItems: 10, status: 'Completed' }; return finalReport; // This object is the workflow's result};Ensuring data integrity is crucial for robust workflows. IdentityFlow integrates seamlessly with validation libraries that adhere to the Standard Schema specification (StandardSchemaV1), allowing you to define and enforce schemas for workflow parameters and activity results.
Built-in Support (Valibot):
IdentityFlow includes Valibot out-of-the-box. You do not need to install it separately. Access its functions conveniently via the SDK export:
import * as v from '@identity-flow/sdk/valibot';Using Other Libraries (Standard Schema):
You can use any library compatible with StandardSchemaV1 (like Zod, ArkType, Effect Schema, etc.). Simply import the library as usual and pass its schema objects where needed.
Schema Locations:
You can apply schemas in several places:
-
Workflow Definition (
defineWorkflow): Validates the initialparamspassed when starting a workflow instance.import * as v from '@identity-flow/sdk/valibot';// Example using Zod (assuming zod is installed in the project)// import * as z from 'zod';// const zodSchema = z.object({ /* ... */ });export default defineWorkflow({name: 'process-order',version: '1.0.0',// Pass any StandardSchemaV1 compatible schema:schema: v.object({// Validate flow.params using built-in ValibotorderId: v.string([v.uuid()]),customerId: v.string(),amount: v.number([v.minValue(0)]),items: v.array(v.object({ sku: v.string(), quantity: v.number() })),}),},async (flow) => {// flow.params is guaranteed to match the schema hereconst { orderId, amount } = flow.params;// ...},);If the input parameters provided when starting the workflow don’t match the schema, the instance creation will fail immediately.
-
Activity Options (
flow.do,flow.dialog,flow.request,flow.start): Validates the result returned by the activity or the data submitted externally to continue adialogorrequest.// Inside execute function:// Validate the result of a flow.do taskconst userInfo = await flow.do('fetch-user-profile',{ schema: v.object({ name: v.string(), email: v.string([v.email()]) }) },async ({ use }) => {const apiClient = use(ApiClientBinding); // Assuming ApiClientBinding is definedreturn await apiClient.getUserProfile(flow.params.userId);},);// userInfo is guaranteed to match the schema here// Validate the data submitted to continue a dialogconst approval = await flow.dialog('manager-approval',{ schema: v.object({ approved: v.boolean(), comments: v.optional(v.string()) }) },({ token }) => ({params: {/* ... */},}),);// approval is guaranteed to match the schema hereIf the data returned by the activity or submitted via
continuedoesn’t match the schema provided in the options, the step will fail and potentially retry.
Benefits:
- Type Safety: Ensures data conforms to expected structures throughout the workflow.
- Early Error Detection: Catches invalid data close to the source.
- Clear Contracts: Defines clear expectations for inputs and outputs of workflows and steps.
Leverage schema validation using your preferred Standard Schema library to build more reliable and predictable workflows.