@hemia/workflow-core
Version:
Core del sistema de orquestación de workflows de Hemia. Permite definir, ejecutar y probar flujos personalizados.
296 lines (210 loc) • 7.06 kB
Markdown
# @hemia/workflow-core
`@hemia/workflow-core` es el núcleo de un sistema de orquestación de flujos (workflows), pensado para ser modular, extensible y fácilmente integrable. Define los tipos, utilidades y herramientas necesarias para ejecutar, probar e interpolar pasos dentro de un workflow.
## 🚀 Características
* Definición estructurada de workflows y pasos
* Soporte para triggers manuales, por webhook y programados (cron)
* Evaluación dinámica de parámetros mediante expresiones (`jexl`)
* Utilidades para manejar contexto de ejecución, variables y logs
* Herramientas para pruebas unitarias de nodos
* Mecanismo de salida e interpolación entre pasos
## 📦 Instalación
```bash
npm install @hemia/workflow-core
```
## 🧩 Estructura de un Workflow
```ts
const workflow: Workflow = {
id: 'order-approval',
name: 'Aprobación de Pedido',
trigger: { type: 'manual' },
steps: [
{
id: 'start',
name: 'Inicio',
type: 'noop',
next: [{ id: 'validate' }],
params: {},
},
{
id: 'validate',
name: 'Validar Pedido',
type: 'http',
next: [{ id: 'log' }],
params: {
method: 'POST',
url: 'https://api.example.com/validate',
body: {
orderId: '{{variables.orderId}}'
}
}
},
{
id: 'log',
name: 'Registrar resultado',
type: 'log',
params: {
message: 'Resultado: {{output_validate.status}}'
}
}
],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
```
## ⚙️ Tipos clave
### `Workflow`
Define el flujo general, los pasos y cómo se activa (manual, webhook o cron).
### `WorkflowStep`
Cada paso define su tipo, parámetros (`params`), y los pasos siguientes (`next`).
### `ExecutionContext`
Contexto disponible durante la ejecución. Permite:
* Guardar y obtener variables
* Registrar logs
* Recuperar resultados (`output_{{stepId}}`)
## 🔧 Utilidades principales
### 📌 `interpolate(template, context)`
Evalúa expresiones dentro de strings usando `{{}}` y el contexto de ejecución:
```ts
await interpolate('Pedido ID: {{variables.orderId}}', context);
```
### 🧮 `interpolateStepParams(params, context)`
Evalúa todos los valores de `params` dinámicamente:
```ts
const resolved = await interpolateStepParams(step.params, context);
```
### 📤 `setOutput(stepId, data, context)`
Guarda el resultado de un paso:
```ts
setOutput('validate', { status: 'approved' }, context);
```
### 📥 `getOutput(stepId, context)`
Obtiene la salida de un paso anterior:
```ts
const result = getOutput('validate', context);
```
### 📚 `contextHelpers`
Funciones auxiliares para variables y logs:
```ts
setVar('foo', 123, context);
const value = getVar('foo', context, 0);
logInfo(context, 'Mensaje informativo');
```
## 🔧 Interpolación avanzada: ${config.*} y transforms JEXL
El core soporta interpolación de:
- Variables con `{{ ... }}` usando JEXL sobre `context.variables`.
- Placeholders de configuración `${config.*}` en strings. El engine inyecta `variables.config` desde `workflow.config` (o `workflow.metadata.config`).
Ejemplos:
```ts
// String con ${config.*}
const url = "${config.apiUrl}/vacations"; // resuelve desde variables.config.apiUrl
// Expresiones JEXL
"{{ input.employeeId }}"
"{{ output_checkVacationBalance.data.availableDays }}"
```
### Transforms de JEXL (funciones en templates con pipe)
Puedes registrar funciones globales para usarlas en templates con la sintaxis de pipe:
```ts
import { registerJexlTransforms, addJexlTransform } from '@hemia/workflow-core';
registerJexlTransforms({
getManagerEmail: (employeeId: string) => lookupEmail(employeeId),
generateApprovalUrl: (requestId: string) => `${process.env.APP_URL}/approve/${requestId}`,
});
// O individualmente
addJexlTransform('safeLower', (s: any) => (s ?? '').toString().toLowerCase());
```
Uso en templates:
```jsonc
{
"to": "{{ input.employeeId | getManagerEmail }}",
"approvalUrl": "{{ input.requestId | generateApprovalUrl }}",
"slug": "{{ input.name | safeLower }}"
}
```
Notas:
- Los transforms son globales al proceso y no requieren inyección por ejecución.
- Mantén las funciones puras y rápidas; se ejecutan durante la interpolación.
- `${config.*}` solo resuelve claves bajo `config`.
## 🧪 Testing
Puedes testear tus nodos individualmente usando `testNode`:
```ts
import { testNode } from '@hemia/workflow-core';
const myNode: WorkflowNode = {
type: 'noop',
async execute(params, context) {
return { success: true, output: { test: true } };
}
};
const { result, context } = await testNode(myNode, { foo: '{{1 + 1}}' });
```
## 🧱 Extender con nodos personalizados
```ts
const CustomNode: WorkflowNode = {
type: 'custom',
async execute(params, context) {
context.log('Ejecutando nodo personalizado');
return { success: true, output: { done: true } };
}
};
```
## 🧨 Manejo de errores
* `WorkflowValidationError`: estructura inválida del workflow
* `WorkflowExecutionError`: error durante la ejecución de un paso
* `ExecutionResult.error`: detalles específicos del nodo
## 🧰 Exports
```ts
// Tipos
import { Workflow, WorkflowStep, ExecutionContext, WorkflowNode } from '@hemia/workflow-core';
// Utilidades de interpolación y contexto
import {
interpolate,
interpolateStepParams,
interpolateOutput,
setOutput,
getOutput,
contextHelpers,
testNode,
MockExecutionContext
} from '@hemia/workflow-core';
```
## 🧰 Utilidades de inspección y evaluación de expresiones
Estas funciones ayudan a detectar y trabajar con expresiones interpoladas y condiciones dentro de los workflows.
### Detección de expresiones interpolables
- **isInterpolatable(value): boolean**
Indica si un valor puede contener expresiones interpoladas (`string`, `array` u `object`).
- **isStringInterpolatable(value): boolean**
Indica si un string contiene una expresión `{{ ... }}`.
- **isArrayInterpolatable(value): boolean**
Indica si algún elemento de un array es interpolable.
- **isObjectInterpolatable(value): boolean**
Indica si algún valor de un objeto es interpolable.
- **isTemplateInterpolatable(template): boolean**
Indica si un template (string, array u objeto) es interpolable.
- **isVariableExpression(expression): boolean**
Indica si un string es una expresión de variable simple, como `{{foo.bar}}`.
#### Ejemplo
```ts
isInterpolatable({ foo: '{{bar}}' }); // true
isStringInterpolatable('Hola {{nombre}}'); // true
isArrayInterpolatable(['a', '{{b}}']); // true
isObjectInterpolatable({ a: 1, b: '{{x}}' }); // true
isTemplateInterpolatable(['{{x}}', 2]); // true
isVariableExpression('{{foo.bar}}'); // true
```
--
## 🧠 Contribuye
¿Quieres agregar nodos, validaciones o mejoras al motor de ejecución? Este paquete es el corazón del sistema, y puede ser extendido fácilmente.
## 📄 Licencia
MIT © Hemia Technologies