npm package: @hemia/workflow-core
Description: Core del sistema de orquestación de workflows de Hemia. Permite definir, ejecutar y probar flujos personalizados.
Author: Hemia Technologies
Latest version: 0.0.6

# @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