### Install dependencies Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Run this command to install all required project dependencies. ```bash npm install ``` -------------------------------- ### Install Dagflow.js Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Install the dagflowjs package using npm. ```bash npm install dagflowjs ``` -------------------------------- ### execute Method Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Runs the complete workflow starting from an initial context and returns the final state and execution metrics. ```APIDOC ## execute(initialContext) ### Description Runs the complete workflow starting from an initial context. Returns a DagResult containing success status, final context, comprehensive metrics, and any error that occurred. ### Parameters #### Request Body - **initialContext** (object) - Required - The starting state object for the workflow execution. ### Response #### Success Response - **success** (boolean) - Indicates if the workflow completed successfully. - **context** (object) - The final state of the workflow context. - **metrics** (object) - Comprehensive execution metrics including total nodes, duration, and per-node status. - **error** (object) - Contains error details if the workflow failed. ``` -------------------------------- ### Basic DAG Workflow Example Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Defines a workflow for processing an order, including validation, payment, inventory, shipping, and completion. Use this to set up a typical order processing flow. ```typescript import { DagEngine } from 'dagflowjs'; interface OrderContext { orderId: string; paymentId?: string; inventoryReserved: boolean; shippingLabel?: string; orderStatus: 'pending' | 'processing' | 'completed'; } const engine = new DagEngine({ logger: { info: (msg) => console.log(`[INFO] ${msg}`), warn: (msg) => console.warn(`[WARN] ${msg}`), error: (msg) => console.error(`[ERROR] ${msg}`), }, }); engine .addNode({ id: 'validate-order', execute: async (ctx) => { return { orderStatus: 'processing' as const }; }, }) .addNode({ id: 'process-payment', dependsOn: ['validate-order'], config: { maxRetries: 3, timeoutMs: 30000 }, shouldRun: async (ctx) => { // Gate: blocks dependents if false return ctx.orderStatus === 'processing'; }, validate: async (ctx) => { // Validation: skips node but allows dependents return !!ctx.orderId; }, execute: async (ctx) => { return { paymentId: 'pay_12345' }; }, }) .addNode({ id: 'reserve-inventory', dependsOn: ['validate-order'], validate: async (ctx) => { return ctx.orderStatus === 'processing'; }, execute: async (ctx) => { return { inventoryReserved: true }; }, }) .addNode({ id: 'create-shipping-label', dependsOn: ['process-payment', 'reserve-inventory'], shouldRun: async (ctx) => { return !!ctx.paymentId && ctx.inventoryReserved === true; }, execute: async (ctx) => { return { shippingLabel: 'LABEL_67890' }; }, }) .addNode({ id: 'complete-order', dependsOn: ['create-shipping-label'], validate: async (ctx) => { return !!ctx.shippingLabel; }, execute: async (ctx) => { return { orderStatus: 'completed' as const }; }, }); const result = await engine.execute({ orderId: 'ORD_001', inventoryReserved: false, orderStatus: 'pending', }); if (result.success) { console.log('Success:', result.context); console.log('Metrics:', result.metrics); } ``` -------------------------------- ### Build the library Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Compile the library for production use. ```bash npm run build ``` -------------------------------- ### Run unit tests Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Execute the project's unit test suite. ```bash npm run test ``` -------------------------------- ### Initialize DagEngine Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Configure the engine with a logger and optional custom planning logic. ```typescript import { DagEngine, Logger, DagNodeDeps } from 'dagflowjs'; // Define your context type interface WorkflowContext { userId: string; data?: Record; processed: boolean; } // Create logger implementation const logger: Logger = { info: (msg, meta?) => console.log(`[INFO] ${msg}`, meta || ''), warn: (msg, meta?) => console.warn(`[WARN] ${msg}`, meta || ''), error: (msg, meta?) => console.error(`[ERROR] ${msg}`, meta || ''), }; // Initialize the engine const deps: DagNodeDeps = { logger }; const engine = new DagEngine(deps); // Optional: Use a custom planner const customEngine = new DagEngine(deps, (nodes) => { // Custom planning logic - must return { order: string[], batches: string[][] } return { order: nodes.map(n => n.id), batches: [nodes.map(n => n.id)], // All nodes in one batch }; }); ``` -------------------------------- ### Configure DagNodeConfig options Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Demonstrates setting timeouts, exponential backoff retries, and various error handling strategies like fail, skip, and skip-dependents. ```typescript import { DagEngine, DagNodeConfig } from 'dagflowjs'; interface ApiContext { response?: string; fallbackUsed: boolean; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); // Node with timeout - aborts if execution exceeds 5 seconds engine.addNode({ id: 'api-call', config: { timeoutMs: 5000, }, execute: async (ctx, deps, signal) => { // signal.aborted becomes true on timeout const response = await fetch('https://api.example.com/data', { signal }); return { response: await response.text() }; }, }); // Node with retries and exponential backoff // Delays: 100ms, 200ms, 400ms between retries engine.addNode({ id: 'flaky-service', config: { maxRetries: 3, // 4 total attempts (1 initial + 3 retries) retryDelayMs: 100, // Base delay, doubles each retry }, execute: async (ctx) => { const result = await callFlakyService(); return { response: result }; }, }); // Error strategy: 'fail' (default) - stops entire workflow engine.addNode({ id: 'critical-step', config: { onError: 'fail' }, execute: async () => { throw new Error('Critical failure'); // Workflow stops, result.success = false }, }); // Error strategy: 'skip' - continues workflow, marks node as failed engine.addNode({ id: 'optional-step', config: { onError: 'skip' }, execute: async () => { throw new Error('Non-critical failure'); // Workflow continues, dependent nodes still run }, }); // Error strategy: 'skip-dependents' - blocks all dependent nodes engine.addNode({ id: 'gateway-step', config: { onError: 'skip-dependents' }, execute: async () => { throw new Error('Gateway failure'); // Workflow continues but all dependents are blocked }, }); ``` -------------------------------- ### plan Method Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Pre-computes the execution plan showing the order and parallel batches without running the workflow. ```APIDOC ## plan() ### Description Pre-computes the execution plan showing the order and parallel batches without running the workflow. Useful for visualization, debugging, or validating the DAG structure before execution. ### Response #### Success Response - **order** (array) - A flat list of node IDs in the order they will be executed. - **batches** (array) - A list of batches, where each batch contains node IDs that can be executed in parallel. ``` -------------------------------- ### DagEngine Constructor Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Initializes the main engine class for orchestrating DAG workflow execution. ```APIDOC ## DagEngine Constructor ### Description The main engine class that orchestrates DAG workflow execution. It accepts a dependencies object containing a logger and an optional custom planner function. ### Parameters - **deps** (DagNodeDeps) - Required - Object containing a logger implementation. - **planner** (Function) - Optional - Custom function to control execution order. Must return { order: string[], batches: string[][] }. ``` -------------------------------- ### Implement Order Fulfillment Workflow with DagEngine Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Defines a multi-step workflow including customer verification, payment processing, inventory reservation, shipping, and notifications. Uses dependency management to ensure correct execution order and handles partial failures. ```typescript import { DagEngine, DagResult } from 'dagflowjs'; interface FulfillmentContext { orderId: string; customerId: string; items: { sku: string; quantity: number }[]; // Populated during execution customerVerified: boolean; paymentId?: string; inventoryReserved: boolean; warehouseId?: string; shippingLabel?: string; trackingNumber?: string; notificationSent: boolean; status: 'pending' | 'processing' | 'shipped' | 'failed'; } const engine = new DagEngine({ logger: { info: (msg, meta) => console.log(`[INFO] ${msg}`, meta || ''), warn: (msg, meta) => console.warn(`[WARN] ${msg}`, meta || ''), error: (msg, meta) => console.error(`[ERROR] ${msg}`, meta || ''), }, }); // Step 1: Verify customer (gate - blocks all if fraud detected) engine.addNode({ id: 'verify-customer', shouldRun: async (ctx) => { const isFraud = await checkFraudScore(ctx.customerId); return !isFraud; // Block entire workflow if fraud }, execute: async (ctx) => ({ customerVerified: true, status: 'processing' as const, }), }); // Step 2a: Process payment (parallel with inventory) engine.addNode({ id: 'process-payment', dependsOn: ['verify-customer'], config: { maxRetries: 3, retryDelayMs: 1000, timeoutMs: 30000 }, execute: async (ctx, deps, signal) => { const paymentId = await chargeCustomer(ctx.customerId, ctx.items, signal); deps.logger.info(`Payment processed: ${paymentId}`); return { paymentId }; }, cleanup: async (ctx) => { await logPaymentAttempt(ctx.orderId, ctx.paymentId); }, }); // Step 2b: Reserve inventory (parallel with payment) engine.addNode({ id: 'reserve-inventory', dependsOn: ['verify-customer'], config: { onError: 'skip-dependents' }, execute: async (ctx) => { const warehouseId = await reserveItems(ctx.items); return { inventoryReserved: true, warehouseId }; }, }); // Step 3: Create shipping label (needs both payment and inventory) engine.addNode({ id: 'create-shipping', dependsOn: ['process-payment', 'reserve-inventory'], validate: (ctx) => !!ctx.paymentId && ctx.inventoryReserved, config: { timeoutMs: 10000 }, execute: async (ctx) => { const { label, tracking } = await generateShippingLabel(ctx.warehouseId!, ctx.orderId); return { shippingLabel: label, trackingNumber: tracking }; }, }); // Step 4: Update order status engine.addNode({ id: 'complete-order', dependsOn: ['create-shipping'], execute: async (ctx) => ({ status: 'shipped' as const, }), }); // Step 5: Send notification (optional, don't fail workflow) engine.addNode({ id: 'send-notification', dependsOn: ['complete-order'], config: { onError: 'skip', maxRetries: 2 }, execute: async (ctx) => { await sendEmail(ctx.customerId, { subject: `Order ${ctx.orderId} Shipped!`, trackingNumber: ctx.trackingNumber, }); return { notificationSent: true }; }, }); // Execute the workflow async function fulfillOrder(order: Omit) { const result = await engine.execute({ ...order, customerVerified: false, inventoryReserved: false, notificationSent: false, status: 'pending', }); if (result.success) { console.log(`Order ${order.orderId} fulfilled successfully`); console.log(`Tracking: ${result.context.trackingNumber}`); } else { console.error(`Order ${order.orderId} failed: ${result.error?.message}`); // Handle partial completion based on context if (result.context.paymentId && !result.context.shippingLabel) { await refundPayment(result.context.paymentId); } } // Log execution summary console.log('Execution Summary:', { duration: `${result.metrics.finishedAt! - result.metrics.startedAt}ms`, successful: result.metrics.successfulNodes, failed: result.metrics.failedNodes, skipped: result.metrics.skippedNodes, blocked: result.metrics.blockedNodes, }); return result; } // Usage await fulfillOrder({ orderId: 'ORD-2024-001', customerId: 'CUST-123', items: [ { sku: 'WIDGET-A', quantity: 2 }, { sku: 'GADGET-B', quantity: 1 }, ], }); ``` -------------------------------- ### DagEngine Constructor Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Initializes the DagEngine with a generic context type and dependency configurations, including a logger. ```typescript new DagEngine(deps: DagNodeDeps) ``` -------------------------------- ### Execute a workflow with DagEngine Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Runs a defined DAG workflow and returns a result object containing the final context and execution metrics. Use this to process workflows and handle success or failure states. ```typescript import { DagEngine, DagResult } from 'dagflowjs'; interface OrderContext { orderId: string; paymentId?: string; inventoryReserved: boolean; shippingLabel?: string; orderStatus: 'pending' | 'processing' | 'completed' | 'failed'; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); engine .addNode({ id: 'validate-order', execute: async (ctx) => ({ orderStatus: 'processing' as const }), }) .addNode({ id: 'process-payment', dependsOn: ['validate-order'], config: { maxRetries: 3, timeoutMs: 30000 }, execute: async (ctx) => ({ paymentId: 'pay_12345' }), }) .addNode({ id: 'reserve-inventory', dependsOn: ['validate-order'], execute: async (ctx) => ({ inventoryReserved: true }), }) .addNode({ id: 'create-shipping', dependsOn: ['process-payment', 'reserve-inventory'], execute: async (ctx) => ({ shippingLabel: 'SHIP_67890' }), }) .addNode({ id: 'complete-order', dependsOn: ['create-shipping'], execute: async (ctx) => ({ orderStatus: 'completed' as const }), }); // Execute the workflow const result: DagResult = await engine.execute({ orderId: 'ORD_001', inventoryReserved: false, orderStatus: 'pending', }); if (result.success) { console.log('Order completed:', result.context); // { // orderId: 'ORD_001', // paymentId: 'pay_12345', // inventoryReserved: true, // shippingLabel: 'SHIP_67890', // orderStatus: 'completed' // } } else { console.error('Order failed:', result.error?.message); console.log('Partial context:', result.context); } // Examine execution metrics console.log('Metrics:', { totalNodes: result.metrics.totalNodes, // 5 successfulNodes: result.metrics.successfulNodes, // 5 failedNodes: result.metrics.failedNodes, // 0 skippedNodes: result.metrics.skippedNodes, // 0 blockedNodes: result.metrics.blockedNodes, // 0 duration: result.metrics.finishedAt! - result.metrics.startedAt, // ms }); // Per-node metrics for (const [nodeId, nodeMetrics] of Object.entries(result.metrics.nodes)) { console.log(`${nodeId}: ${nodeMetrics.status} in ${nodeMetrics.durationMs}ms (${nodeMetrics.attempts} attempts)`); } ``` -------------------------------- ### DagNode Interface Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Interface for defining individual workflow nodes and their execution logic. ```APIDOC ## DagNode ### Description Interface for defining workflow nodes, including dependencies, configuration, and lifecycle hooks. ### Properties - **id** (string) - Unique identifier for the node. - **dependsOn** (string[]) - Optional list of node IDs that must complete before this node runs. - **config** (DagNodeConfig) - Optional configuration for retries, timeouts, and error strategies. - **shouldRun** (function) - Determines if a node should execute. If false, the node and its dependents are blocked. - **execute** (function) - The core logic of the node. Returns a partial update to the context. - **validate** (function) - Validates context before execution. If false, the node is skipped but dependents may still run. - **cleanup** (function) - Called after execution for resource management. ``` -------------------------------- ### Plan a workflow execution Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Generates an execution plan without running the workflow. Useful for visualizing the DAG structure and identifying parallel execution batches. ```typescript import { DagEngine, DagPlan } from 'dagflowjs'; interface BuildContext { compiled: boolean; tested: boolean; deployed: boolean; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); engine .addNode({ id: 'lint', execute: async () => ({}) }) .addNode({ id: 'typecheck', execute: async () => ({}) }) .addNode({ id: 'compile', dependsOn: ['lint', 'typecheck'], execute: async () => ({ compiled: true }) }) .addNode({ id: 'unit-tests', dependsOn: ['compile'], execute: async () => ({}) }) .addNode({ id: 'integration-tests', dependsOn: ['compile'], execute: async () => ({}) }) .addNode({ id: 'test-complete', dependsOn: ['unit-tests', 'integration-tests'], execute: async () => ({ tested: true }) }) .addNode({ id: 'deploy', dependsOn: ['test-complete'], execute: async () => ({ deployed: true }) }); const plan: DagPlan = engine.plan(); console.log('Execution order:', plan.order); // ['lint', 'typecheck', 'compile', 'unit-tests', 'integration-tests', 'test-complete', 'deploy'] console.log('Parallel batches:'); plan.batches.forEach((batch, i) => { console.log(` Batch ${i + 1}: [${batch.join(', ')}]`); }); // Batch 1: [lint, typecheck] <- Run in parallel // Batch 2: [compile] <- Waits for batch 1 // Batch 3: [unit-tests, integration-tests] <- Run in parallel // Batch 4: [test-complete] <- Waits for batch 3 // Batch 5: [deploy] <- Final step ``` -------------------------------- ### Using AbortSignal with Fetch Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Pass the AbortSignal to the fetch options to automatically cancel the request if the signal is aborted. This is useful for handling timeouts. ```typescript import { DagEngine } from 'dagflowjs'; interface DownloadContext { fileUrl: string; content?: string; chunks: string[]; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); engine.addNode({ id: 'download-file', config: { timeoutMs: 10000 }, execute: async (ctx, deps, signal) => { // Use signal with fetch for automatic cancellation const response = await fetch(ctx.fileUrl, { signal }); const content = await response.text(); return { content }; }, }); ``` -------------------------------- ### Implement shouldRun gating hook Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Uses the shouldRun hook to conditionally execute nodes and block dependents based on context or asynchronous checks. ```typescript import { DagEngine } from 'dagflowjs'; interface FeatureContext { userTier: 'free' | 'premium' | 'enterprise'; premiumFeatureResult?: string; enterpriseFeatureResult?: string; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); engine.addNode({ id: 'premium-feature', // Blocks this node AND all dependents if user is not premium+ shouldRun: (ctx) => ctx.userTier === 'premium' || ctx.userTier === 'enterprise', execute: async (ctx) => { return { premiumFeatureResult: 'Premium processing complete' }; }, }); engine.addNode({ id: 'enterprise-analytics', dependsOn: ['premium-feature'], // Further gates for enterprise-only features shouldRun: (ctx) => ctx.userTier === 'enterprise', execute: async (ctx) => { return { enterpriseFeatureResult: 'Enterprise analytics generated' }; }, }); // Async shouldRun for database checks engine.addNode({ id: 'rate-limited-action', shouldRun: async (ctx) => { const allowed = await checkRateLimit(ctx.userTier); return allowed; // false blocks this node and dependents }, execute: async (ctx) => { return {}; }, }); // Execute with free tier - premium-feature and enterprise-analytics are blocked const freeResult = await engine.execute({ userTier: 'free' }); console.log(freeResult.metrics.blockedNodes); // 2 // Execute with premium tier - only enterprise-analytics is blocked const premiumResult = await engine.execute({ userTier: 'premium' }); console.log(premiumResult.metrics.blockedNodes); // 1 ``` -------------------------------- ### DagEngine Class Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md The primary engine class used to define and execute DAG workflows. ```APIDOC ## DagEngine ### Description The main engine class for executing DAG workflows. ### Constructor `new DagEngine(deps: DagNodeDeps)` ### Methods - `addNode(node: DagNode): this` - Adds a node to the workflow definition. - `execute(initial: T): Promise>` - Executes the defined workflow starting with the provided initial context. ``` -------------------------------- ### DagNode Interface Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Defines the structure for a single node within a DAG workflow, including its ID, dependencies, configuration, and execution logic. ```typescript interface DagNode> { id: string; dependsOn?: string[]; config?: DagNodeConfig; shouldRun?(ctx: T): boolean | Promise; execute(ctx: Readonly, deps: DagNodeDeps, signal: AbortSignal): Promise; validate?(ctx: T): boolean | Promise; cleanup?(ctx: T): void | Promise; } ``` -------------------------------- ### Cleanup Hook Source: https://context7.com/abdullah2993/dagflowjs/llms.txt The cleanup hook is called after node execution completes, regardless of success or failure. It is suitable for resource cleanup, logging, or notifications. It is not called if `shouldRun` or `validate` returns false. ```APIDOC ## cleanup Hook A function called after node execution completes, regardless of success or failure. Use for resource cleanup, logging, or notifications. Not called if `shouldRun` or `validate` returns false. ### Example Usage: ```typescript import { DagEngine } from 'dagflowjs'; interface ResourceContext { resourceId?: string; connectionOpen: boolean; result?: string; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); engine.addNode({ id: 'use-resource', execute: async (ctx) => { const resourceId = await acquireResource(); return { resourceId, connectionOpen: true }; }, // Called after execute completes (success or failure) cleanup: async (ctx) => { if (ctx.resourceId) { await releaseResource(ctx.resourceId); console.log(`Released resource: ${ctx.resourceId}`); } }, }); // Cleanup for logging and metrics engine.addNode({ id: 'tracked-operation', execute: async (ctx) => { const startTime = Date.now(); const result = await performOperation(); return { result }; }, cleanup: async (ctx) => { await logToAnalytics({ operation: 'tracked-operation', success: ctx.result !== undefined, timestamp: new Date().toISOString(), }); }, }); // Cleanup runs even on execute failure engine.addNode({ id: 'may-fail', config: { onError: 'skip' }, execute: async (ctx) => { throw new Error('Operation failed'); }, cleanup: async (ctx) => { // This still runs after the error await notifyTeam('Operation may-fail completed with error'); }, }); ``` ### Parameters for `cleanup` hook: - `ctx` (ResourceContext): The context object containing data and state for the current node. ``` -------------------------------- ### addNode Method Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Adds a workflow node to the DAG with execution logic and dependencies. ```APIDOC ## addNode Method ### Description Adds a workflow node to the DAG with its execution logic, dependencies, and optional configuration. Returns the engine instance for method chaining. ### Parameters - **node** (DagNode) - Required - Object containing: - **id** (string) - Unique identifier for the node. - **dependsOn** (string[]) - Optional - Array of node IDs that must complete before this node executes. - **execute** (Function) - Required - Async function containing the node logic, receiving the context and returning partial context updates. ### Errors - Throws an error if a node with the same ID already exists. ``` -------------------------------- ### Listening for Abort Events Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Attach an event listener to the 'abort' event of the AbortSignal. This allows you to perform cleanup actions or reject promises when the operation is cancelled. ```typescript // Listen for abort event engine.addNode({ id: 'streaming-operation', config: { timeoutMs: 5000 }, execute: async (ctx, deps, signal) => { return new Promise((resolve, reject) => { const stream = createDataStream(); signal.addEventListener('abort', () => { stream.destroy(); reject(new Error('Operation timed out')); }); stream.on('end', () => resolve({})); }); }, }); ``` -------------------------------- ### DagNodeConfig Interface Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Specifies configuration options for a DAG node, such as timeouts, retry attempts, retry delay, and error handling strategy. ```typescript interface DagNodeConfig { timeoutMs?: number; // Node timeout in milliseconds maxRetries?: number; // Maximum retry attempts (default: 0) retryDelayMs?: number; // Base delay between retries (default: 500ms) onError?: 'fail' | 'skip' | 'skip-dependents'; // Error handling strategy (default: 'fail') } ``` -------------------------------- ### DagResult Interface Source: https://github.com/abdullah2993/dagflowjs/blob/master/README.md Represents the outcome of a DAG workflow execution, including success status, the final context, and execution metrics. ```typescript interface DagResult { success: boolean; context: T; metrics: DagMetrics; error?: Error; } ``` -------------------------------- ### Implement cleanup Hook in Dagflowjs Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Use the cleanup hook to perform post-execution tasks like resource release or logging. This hook runs regardless of success or failure, provided the node was not skipped by validate or shouldRun. ```typescript import { DagEngine } from 'dagflowjs'; interface ResourceContext { resourceId?: string; connectionOpen: boolean; result?: string; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); engine.addNode({ id: 'use-resource', execute: async (ctx) => { const resourceId = await acquireResource(); return { resourceId, connectionOpen: true }; }, // Called after execute completes (success or failure) cleanup: async (ctx) => { if (ctx.resourceId) { await releaseResource(ctx.resourceId); console.log(`Released resource: ${ctx.resourceId}`); } }, }); // Cleanup for logging and metrics engine.addNode({ id: 'tracked-operation', execute: async (ctx) => { const startTime = Date.now(); const result = await performOperation(); return { result }; }, cleanup: async (ctx) => { await logToAnalytics({ operation: 'tracked-operation', success: ctx.result !== undefined, timestamp: new Date().toISOString(), }); }, }); // Cleanup runs even on execute failure engine.addNode({ id: 'may-fail', config: { onError: 'skip' }, execute: async (ctx) => { throw new Error('Operation failed'); }, cleanup: async (ctx) => { // This still runs after the error await notifyTeam('Operation may-fail completed with error'); }, }); ``` -------------------------------- ### Validate Hook Source: https://context7.com/abdullah2993/dagflowjs/llms.txt The validate hook checks preconditions before node execution. If it returns false, only the current node is skipped, allowing dependent nodes to proceed. ```APIDOC ## validate Hook A validation function that checks preconditions before node execution. Unlike `shouldRun`, when `validate` returns false, only the current node is skipped while dependent nodes can still execute. ### Example Usage: ```typescript import { DagEngine } from 'dagflowjs'; interface DataContext { inputData?: string[]; processedData?: string[]; reportGenerated: boolean; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); engine.addNode({ id: 'process-data', // Skip if no input data, but allow dependents to continue validate: (ctx) => !!ctx.inputData && ctx.inputData.length > 0, execute: async (ctx) => { const processed = ctx.inputData!.map(item => item.toUpperCase()); return { processedData: processed }; }, }); engine.addNode({ id: 'generate-report', dependsOn: ['process-data'], // This node can still run even if process-data was skipped validate: (ctx) => ctx.processedData !== undefined, execute: async (ctx) => { return { reportGenerated: true }; }, }); // Async validation example engine.addNode({ id: 'external-check', validate: async (ctx) => { const isValid = await externalValidationService(ctx); return isValid; }, execute: async (ctx) => { return {}; }, }); // Execute without input data const result = await engine.execute({ reportGenerated: false }); console.log(result.metrics.nodes['process-data'].status); // 'skipped' console.log(result.metrics.nodes['generate-report'].status); // 'skipped' (no processedData) ``` ### Parameters for `validate` hook: - `ctx` (DataContext): The context object containing data and state for the current node. ``` -------------------------------- ### Add Nodes to DAG Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Define workflow nodes with execution logic and dependencies. Nodes can be added individually or chained. ```typescript import { DagEngine, DagNode } from 'dagflowjs'; interface OrderContext { orderId: string; validated: boolean; paymentId?: string; shipped: boolean; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); // Basic node with just execute engine.addNode({ id: 'validate-order', execute: async (ctx) => { // Perform validation logic return { validated: true }; }, }); // Node with dependencies - waits for validate-order to complete engine.addNode({ id: 'process-payment', dependsOn: ['validate-order'], execute: async (ctx) => { if (!ctx.validated) throw new Error('Order not validated'); return { paymentId: `PAY_${Date.now()}` }; }, }); // Node with multiple dependencies - waits for both to complete engine.addNode({ id: 'ship-order', dependsOn: ['validate-order', 'process-payment'], execute: async (ctx) => { return { shipped: true }; }, }); // Chained node additions engine .addNode({ id: 'node-a', execute: async () => ({}) }) .addNode({ id: 'node-b', dependsOn: ['node-a'], execute: async () => ({}) }); ``` -------------------------------- ### Implement validate Hook in Dagflowjs Source: https://context7.com/abdullah2993/dagflowjs/llms.txt Use the validate hook to check preconditions before node execution. If it returns false, the node is skipped but dependent nodes may still proceed. ```typescript import { DagEngine } from 'dagflowjs'; interface DataContext { inputData?: string[]; processedData?: string[]; reportGenerated: boolean; } const engine = new DagEngine({ logger: { info: console.log, warn: console.warn, error: console.error } }); engine.addNode({ id: 'process-data', // Skip if no input data, but allow dependents to continue validate: (ctx) => !!ctx.inputData && ctx.inputData.length > 0, execute: async (ctx) => { const processed = ctx.inputData!.map(item => item.toUpperCase()); return { processedData: processed }; }, }); engine.addNode({ id: 'generate-report', dependsOn: ['process-data'], // This node can still run even if process-data was skipped validate: (ctx) => ctx.processedData !== undefined, execute: async (ctx) => { return { reportGenerated: true }; }, }); // Async validation example engine.addNode({ id: 'external-check', validate: async (ctx) => { const isValid = await externalValidationService(ctx); return isValid; }, execute: async (ctx) => { return {}; }, }); // Execute without input data const result = await engine.execute({ reportGenerated: false }); console.log(result.metrics.nodes['process-data'].status); // 'skipped' console.log(result.metrics.nodes['generate-report'].status); // 'skipped' (no processedData) ``` -------------------------------- ### Periodic AbortSignal Checks in Loops Source: https://context7.com/abdullah2993/dagflowjs/llms.txt For long-running loops, periodically check `signal.aborted` to allow for cooperative cancellation. This prevents the loop from continuing indefinitely if a timeout occurs. ```typescript engine.addNode({ id: 'process-chunks', config: { timeoutMs: 30000 }, execute: async (ctx, deps, signal) => { const chunks: string[] = []; const items = ctx.content?.split('\n') || []; for (const item of items) { // Check signal periodically in long loops if (signal.aborted) { deps.logger.warn('Processing aborted due to timeout'); break; } chunks.push(await processItem(item)); } return { chunks }; }, }); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.