UNPKG

business-as-code

Version:

Primitives for expressing business logic and processes as code

248 lines 6.9 kB
/** * Workflow definition (automation sequences) */ /** * Define an automated workflow with triggers and actions * * @example * ```ts * const workflow = Workflow({ * name: 'New Customer Welcome', * description: 'Automated welcome sequence for new customers', * trigger: { * type: 'event', * event: 'Customer.created', * }, * actions: [ * { * order: 1, * type: 'send', * description: 'Send welcome email', * params: { * template: 'welcome_email', * to: '{{customer.email}}', * }, * }, * { * order: 2, * type: 'create', * description: 'Create onboarding task', * params: { * type: 'Task', * title: 'Onboard {{customer.name}}', * assignee: 'customer_success_team', * }, * }, * { * order: 3, * type: 'wait', * description: 'Wait 24 hours', * params: { * duration: '24 hours', * }, * }, * { * order: 4, * type: 'notify', * description: 'Send setup reminder', * params: { * channel: 'email', * message: 'Reminder to complete setup', * }, * condition: 'customer.setupCompleted === false', * }, * ], * }) * ``` */ export function Workflow(definition) { if (!definition.name) { throw new Error('Workflow name is required'); } if (!definition.trigger) { throw new Error('Workflow trigger is required'); } return { ...definition, actions: definition.actions || [], metadata: definition.metadata || {}, }; } /** * Get actions in execution order */ export function getActionsInOrder(workflow) { return [...(workflow.actions || [])].sort((a, b) => a.order - b.order); } /** * Get actions by type */ export function getActionsByType(workflow, type) { return workflow.actions?.filter((action) => action.type === type) || []; } /** * Get conditional actions */ export function getConditionalActions(workflow) { return workflow.actions?.filter((action) => action.condition) || []; } /** * Add action to workflow */ export function addAction(workflow, action) { return { ...workflow, actions: [...(workflow.actions || []), action], }; } /** * Remove action from workflow */ export function removeAction(workflow, order) { const actions = workflow.actions?.filter((a) => a.order !== order); const result = { ...workflow }; if (actions !== undefined) result.actions = actions; return result; } /** * Update action in workflow */ export function updateAction(workflow, order, updates) { const actions = workflow.actions?.map((action) => action.order === order ? { ...action, ...updates } : action); const result = { ...workflow }; if (actions !== undefined) result.actions = actions; return result; } /** * Check if trigger is event-based */ export function isEventTrigger(trigger) { return trigger.type === 'event'; } /** * Check if trigger is schedule-based */ export function isScheduleTrigger(trigger) { return trigger.type === 'schedule'; } /** * Check if trigger is webhook-based */ export function isWebhookTrigger(trigger) { return trigger.type === 'webhook'; } /** * Parse wait duration to milliseconds */ export function parseWaitDuration(duration) { const match = duration.match(/(\d+)\s*(ms|millisecond|milliseconds|s|second|seconds|m|minute|minutes|h|hour|hours|d|day|days)/); if (!match) return 0; const value = parseInt(match[1] || '0', 10); const unit = match[2]; switch (unit) { case 'ms': case 'millisecond': case 'milliseconds': return value; case 's': case 'second': case 'seconds': return value * 1000; case 'm': case 'minute': case 'minutes': return value * 60 * 1000; case 'h': case 'hour': case 'hours': return value * 60 * 60 * 1000; case 'd': case 'day': case 'days': return value * 24 * 60 * 60 * 1000; default: return 0; } } /** * Evaluate condition (simple implementation) */ export function evaluateCondition(condition, context) { // This is a simplified implementation // In production, use a proper expression evaluator try { // Replace variable references with actual values let expression = condition; for (const [key, value] of Object.entries(context)) { const regex = new RegExp(`\\b${key}\\b`, 'g'); expression = expression.replace(regex, JSON.stringify(value)); } // Evaluate the expression (unsafe in production - use a proper evaluator) return Boolean(eval(expression)); } catch { return false; } } /** * Fill template with context values */ export function fillTemplate(template, context) { return template.replace(/\{\{([^}]+)\}\}/g, (_, path) => { const value = getNestedValue(context, path.trim()); return String(value ?? ''); }); } /** * Get nested value from object by path */ function getNestedValue(obj, path) { return path.split('.').reduce((acc, part) => acc?.[part], obj); } /** * Validate workflow definition */ export function validateWorkflow(workflow) { const errors = []; if (!workflow.name) { errors.push('Workflow name is required'); } if (!workflow.trigger) { errors.push('Workflow trigger is required'); } else { if (workflow.trigger.type === 'event' && !workflow.trigger.event) { errors.push('Event trigger must specify an event name'); } if (workflow.trigger.type === 'schedule' && !workflow.trigger.schedule) { errors.push('Schedule trigger must specify a schedule expression'); } if (workflow.trigger.type === 'webhook' && !workflow.trigger.webhook) { errors.push('Webhook trigger must specify a webhook URL'); } } if (workflow.actions) { const orders = new Set(); for (const action of workflow.actions) { if (!action.type) { errors.push(`Action at order ${action.order} must have a type`); } if (orders.has(action.order)) { errors.push(`Duplicate action order: ${action.order}`); } orders.add(action.order); // Validate action-specific requirements if (action.type === 'wait' && !action.params?.['duration']) { errors.push(`Wait action at order ${action.order} must specify duration`); } } } return { valid: errors.length === 0, errors, }; } //# sourceMappingURL=workflow.js.map