Skip to content

Commit ef5698c

Browse files
committed
Merge branch 'dev'
2 parents 513bae6 + 5a554ef commit ef5698c

7 files changed

Lines changed: 500 additions & 277 deletions

File tree

README.md

Lines changed: 95 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# **Symflow: A Flexible Workflow Engine for Node.js**
22

33
[![CI](https://github.com/vandetho/symflow/actions/workflows/ci.yaml/badge.svg)](https://github.com/vandetho/symflow/actions/workflows/ci.yaml)
4-
[![npm version](https://img.shields.io/npm/v/symflow.svg)](https://www.npmjs.com/package/symflow)
4+
[![npm downloads](https://img.shields.io/npm/dw/symflow.svg)](https://www.npmjs.com/package/symflow)
55
[![npm downloads](https://img.shields.io/npm/dm/symflow.svg)](https://www.npmjs.com/package/symflow)
66
[![GitHub stars](https://img.shields.io/github/stars/vandetho/symflow.svg?style=social)](https://github.com/vandetho/symflow/stargazers)
77
[![GitHub issues](https://img.shields.io/github/issues/vandetho/symflow.svg?color=orange)](https://github.com/vandetho/symflow/issues)
8+
[![npm version](https://img.shields.io/npm/v/symflow.svg)](https://www.npmjs.com/package/symflow)
89
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
910

1011
> 🔗 [View on npm »](https://www.npmjs.com/package/symflow)
@@ -223,8 +224,101 @@ You can find a complete example of using **Symflow with Express.js** at: [Symflo
223224
### **Express.js API Support**
224225
- Works **optionally** with Express.js **without modifying the core package**.
225226

227+
228+
---
229+
230+
## **📜 Event Handling in Symflow**
231+
232+
Symflow allows you to **hook into various workflow events** using event listeners.
233+
234+
### 📌 **Available Events**
235+
| Event Type | Description |
236+
|--------------|------------------------------------------------------|
237+
| `ANNOUNCE` | Fires **before** a transition begins. |
238+
| `GUARD` | **Prevents** transitions if conditions are not met. |
239+
| `LEAVE` | Fires **before leaving** a state. |
240+
| `ENTER` | Fires **before entering** a state. |
241+
| `TRANSITION` | Fires **during** a transition. |
242+
| `COMPLETED` | Fires **after** a transition successfully completes. |
243+
| `ENTERED` | Fires **after** a state is successfully entered. |
244+
245+
## **Using Event Listeners**
246+
You can **register event listeners** to customize transition behavior.
247+
248+
### 🛠 **Example: Blocking a Transition with `GUARD`**
249+
```typescript
250+
import { Symflow, WorkflowEventType } from "symflow";
251+
252+
// Define the workflow
253+
const workflowDefinition = {
254+
name: "order_workflow",
255+
stateField: "status",
256+
initialState: ["draft"],
257+
places: { draft: {}, pending: {}, confirmed: {} },
258+
transitions: { approve: { from: ["draft"], to: ["pending"] } },
259+
/* or */
260+
events: {
261+
[WorkflowEventType.GUARD]: [
262+
(event) => {
263+
if (event.entity.userRole !== "admin") {
264+
console.log("❌ Access Denied: Only admins can approve orders.");
265+
return false;
266+
}
267+
return true;
268+
},
269+
],
270+
},
271+
};
272+
273+
// Create a workflow instance
274+
const workflow = new Symflow(workflowDefinition);
275+
276+
// Register a Guard event to prevent unauthorized transitions
277+
workflow.on(WorkflowEventType.GUARD, (event) => {
278+
console.log(`Checking guard for transition "${event.transition}"`);
279+
if (event.entity.userRole !== "admin") {
280+
console.log("❌ Access Denied: Only admins can approve orders.");
281+
return false; // 🚫 Prevent transition
282+
}
283+
return true;
284+
});
285+
286+
// Sample order entity
287+
const order = { id: 1, status: ["draft"], userRole: "customer" };
288+
289+
// Attempt transition
290+
workflow.apply(order, "approve").catch((err) => console.log(err.message));
291+
292+
// Output: ❌ Access Denied: Only admins can approve orders.
293+
```
294+
---
295+
### 📜 **Metadata in Workflow Events**
296+
297+
Metadata can be included in transitions and is accessible inside events.
298+
```typescript
299+
workflow.on(WorkflowEventType.COMPLETED, (event) => {
300+
console.log(`✅ Transition "${event.transition}" completed!`);
301+
console.log(`📌 Metadata:`, event.metadata); // ✅ Metadata is now accessible
302+
});
303+
```
304+
305+
---
306+
307+
### **Example: Logging Transitions with `COMPLETED`**
308+
You can use the `COMPLETED` event to **log successful state changes**.
309+
```typescript
310+
workflow.on(WorkflowEventType.COMPLETED, (event) => {
311+
console.log(`✅ Order ${event.entity.id} successfully transitioned to ${event.toState}`);
312+
});
313+
```
314+
---
315+
### 📡 **EventEmitter Integration**
316+
Symflow supports emitting events via Node.js `EventEmitter` for full flexibility and code-splitting.
317+
[event-emitter.md](doc/event-emitter.md)
318+
226319
---
227320

321+
228322
## **📚 API Reference**
229323
### **`new Symflow(definition)`**
230324
- **Defines a new workflow** that can be used globally.
@@ -326,89 +420,5 @@ transitions: {
326420
}
327421
```
328422

329-
---
330-
331-
## **📜 Event Handling in Symflow**
332-
Symflow allows you to **hook into various workflow events** using event listeners.
333-
### 📌 **Available Events**
334-
| Event Type | Description |
335-
|--------------|------------------------------------------------------|
336-
| `ANNOUNCE` | Fires **before** a transition begins. |
337-
| `GUARD` | **Prevents** transitions if conditions are not met. |
338-
| `LEAVE` | Fires **before leaving** a state. |
339-
| `ENTER` | Fires **before entering** a state. |
340-
| `TRANSITION` | Fires **during** a transition. |
341-
| `COMPLETED` | Fires **after** a transition successfully completes. |
342-
| `ENTERED` | Fires **after** a state is successfully entered. |
343-
344-
## **Using Event Listeners**
345-
You can **register event listeners** to customize transition behavior.
346-
347-
### 🛠 **Example: Blocking a Transition with `GUARD`**
348-
```typescript
349-
import { Symflow, WorkflowEventType } from "symflow";
350-
351-
// Define the workflow
352-
const workflowDefinition = {
353-
name: "order_workflow",
354-
stateField: "status",
355-
initialState: ["draft"],
356-
places: { draft: {}, pending: {}, confirmed: {} },
357-
transitions: { approve: { from: ["draft"], to: ["pending"] } }
358-
/* or */
359-
events: {
360-
[WorkflowEventType.GUARD]: [
361-
(event) => {
362-
if (event.entity.userRole !== "admin") {
363-
console.log("❌ Access Denied: Only admins can approve orders.");
364-
return false;
365-
}
366-
return true;
367-
},
368-
],
369-
},
370-
};
371-
372-
// Create a workflow instance
373-
const workflow = new Symflow(workflowDefinition);
374-
375-
// Register a Guard event to prevent unauthorized transitions
376-
workflow.on(WorkflowEventType.GUARD, (event) => {
377-
console.log(`Checking guard for transition "${event.transition}"`);
378-
if (event.entity.userRole !== "admin") {
379-
console.log("❌ Access Denied: Only admins can approve orders.");
380-
return false; // 🚫 Prevent transition
381-
}
382-
return true;
383-
});
384-
385-
// Sample order entity
386-
const order = { id: 1, status: ["draft"], userRole: "customer" };
387-
388-
// Attempt transition
389-
workflow.apply(order, "approve").catch((err) => console.log(err.message));
390-
391-
// Output: ❌ Access Denied: Only admins can approve orders.
392-
```
393-
---
394-
### 📜 **Metadata in Workflow Events**
395-
396-
Metadata can be included in transitions and is accessible inside events.
397-
```typescript
398-
workflow.on(WorkflowEventType.COMPLETED, (event) => {
399-
console.log(`✅ Transition "${event.transition}" completed!`);
400-
console.log(`📌 Metadata:`, event.metadata); // ✅ Metadata is now accessible
401-
});
402-
```
403-
404-
---
405-
406-
### **Example: Logging Transitions with `COMPLETED`**
407-
You can use the `COMPLETED` event to **log successful state changes**.
408-
```typescript
409-
workflow.on(WorkflowEventType.COMPLETED, (event) => {
410-
console.log(`✅ Order ${event.entity.id} successfully transitioned to ${event.toState}`);
411-
});
412-
```
413423

414424

doc/event-emitter.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
## 📡 EventEmitter Integration
2+
3+
Symflow supports emitting events via Node.js `EventEmitter` for full flexibility and code-splitting.
4+
5+
### 🔧 Setup
6+
7+
```ts
8+
import { EventEmitter } from 'events';
9+
import { Symflow } from 'symflow';
10+
import type { WorkflowDefinition } from 'symflow';
11+
12+
const emitter = new EventEmitter();
13+
14+
const workflowDefinition: WorkflowDefinition<any> = {
15+
name: 'article',
16+
type: 'workflow',
17+
stateField: 'state',
18+
initialState: ['draft'],
19+
places: {
20+
draft: {}, review: {}, published: {}
21+
},
22+
transitions: {
23+
submit: { from: 'draft', to: 'review' },
24+
publish: { from: 'review', to: 'published' }
25+
}
26+
};
27+
28+
const workflow = new Symflow(workflowDefinition, emitter);
29+
```
30+
31+
### 📢 Namespaced Events
32+
33+
Symflow emits events on these channels:
34+
35+
| Scope | Example |
36+
|----------------|------------------------------------------|
37+
| Global | `symflow.transition` |
38+
| Workflow-level | `symflow.article.transition` |
39+
| Transition-specific | `symflow.article.transition.submit` |
40+
41+
### 🛠 Listen to Events
42+
43+
```ts
44+
emitter.on('symflow.article.transition.submit', (event) => {
45+
console.log('📦 Submit triggered for article', event.entity.id);
46+
});
47+
48+
emitter.on('symflow.transition', (event) => {
49+
console.log(`[Global] ${event.transition} -`, event.entity.id);
50+
});
51+
```
52+
53+
You can use this to separate logic into modules or microservices, decoupling the workflow engine from business logic.

0 commit comments

Comments
 (0)