Skip to content

Commit dbdae13

Browse files
Merge pull request #192 from neuroglia-io/feat-mermaid-diagram
feat: add graph and MermaidJS flowchart
2 parents 06e5a4a + d059bf0 commit dbdae13

File tree

10 files changed

+1361
-21
lines changed

10 files changed

+1361
-21
lines changed

README.md

+136-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
![Node CI](https://github.com/serverlessworkflow/sdk-typescript/workflows/Node%20CI/badge.svg) [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/serverlessworkflow/sdk-typescript)
22

3+
- [Serverless Workflow Specification - TypeScript SDK](#serverless-workflow-specification---typescript-sdk)
4+
- [Status](#status)
5+
- [SDK Structure](#sdk-structure)
6+
- [Types and Interfaces](#types-and-interfaces)
7+
- [Classes](#classes)
8+
- [Fluent Builders](#fluent-builders)
9+
- [Validation Function](#validation-function)
10+
- [Other tools](#other-tools)
11+
- [Getting Started](#getting-started)
12+
- [Installation](#installation)
13+
- [Usage](#usage)
14+
- [Create a Workflow Definition from YAML or JSON](#create-a-workflow-definition-from-yaml-or-json)
15+
- [Create a Workflow Definition by Casting an Object](#create-a-workflow-definition-by-casting-an-object)
16+
- [Create a Workflow Definition Using a Class Constructor](#create-a-workflow-definition-using-a-class-constructor)
17+
- [Create a Workflow Definition Using the Builder API](#create-a-workflow-definition-using-the-builder-api)
18+
- [Serialize a Workflow Definition to YAML or JSON](#serialize-a-workflow-definition-to-yaml-or-json)
19+
- [Validate Workflow Definitions](#validate-workflow-definitions)
20+
- [Generate a directed graph](#generate-a-directed-graph)
21+
- [Generate a MermaidJS flowchart](#generate-a-mermaidjs-flowchart)
22+
- [Building Locally](#building-locally)
23+
324
# Serverless Workflow Specification - TypeScript SDK
425

526
This SDK provides a TypeScript API for working with the [Serverless Workflow Specification](https://github.com/serverlessworkflow/specification).
@@ -14,7 +35,7 @@ The npm [`@serverlessworkflow/sdk`](https://www.npmjs.com/package/@serverlesswor
1435

1536
| Latest Releases | Conformance to Spec Version |
1637
| :---: | :---: |
17-
| [v1.0.0.\*](https://github.com/serverlessworkflow/sdk-typescript/releases/) | [v1.0.0](https://github.com/serverlessworkflow/specification) |
38+
| [v1.0.\*](https://github.com/serverlessworkflow/sdk-typescript/releases/) | [v1.0.0](https://github.com/serverlessworkflow/specification) |
1839

1940
> [!WARNING]
2041
> Previous versions of the SDK were published with a typo in the scope:
@@ -56,6 +77,9 @@ The SDK includes a validation function to check if objects conform to the expect
5677

5778
The `validate` function is directly exported and can be used as `validate('Workflow', workflowObject)`.
5879

80+
### Other Tools
81+
The SDK also ships tools to build directed graph and MermaidJS flowcharts from a workflow.
82+
5983
## Getting Started
6084

6185
### Installation
@@ -86,7 +110,7 @@ do:
86110
set:
87111
variable: 'my first workflow'
88112
`;
89-
const workflowDefinition = Classes.Workflow.deserialize(text);
113+
const workflow = Classes.Workflow.deserialize(text);
90114
```
91115

92116
#### Create a Workflow Definition by Casting an Object
@@ -96,7 +120,7 @@ You can type-cast an object to match the structure of a workflow definition:
96120
import { Classes, Specification, validate } from '@serverlessworkflow/sdk';
97121

98122
// Simply cast an object:
99-
const workflowDefinition = {
123+
const workflow = {
100124
document: {
101125
dsl: '1.0.0',
102126
name: 'test',
@@ -116,9 +140,9 @@ const workflowDefinition = {
116140

117141
// Validate it
118142
try {
119-
validate('Workflow', workflowDefinition);
143+
validate('Workflow', workflow);
120144
// Serialize it
121-
const definitionTxt = Classes.Workflow.serialize(workflowDefinition);
145+
const definitionTxt = Classes.Workflow.serialize(workflow);
122146
}
123147
catch (ex) {
124148
// Invalid workflow definition
@@ -132,7 +156,7 @@ You can create a workflow definition by calling a constructor:
132156
import { Classes, validate } from '@serverlessworkflow/sdk';
133157

134158
// Simply use the constructor
135-
const workflowDefinition = new Classes.Workflow({
159+
const workflow = new Classes.Workflow({
136160
document: {
137161
dsl: '1.0.0',
138162
name: 'test',
@@ -149,7 +173,7 @@ const workflowDefinition = new Classes.Workflow({
149173
},
150174
*/],
151175
});
152-
workflowDefinition.do.push({
176+
workflow.do.push({
153177
step1: new Classes.SetTask({
154178
set: {
155179
variable: 'my first workflow',
@@ -159,9 +183,9 @@ workflowDefinition.do.push({
159183

160184
// Validate it
161185
try {
162-
workflowDefinition.validate();
186+
workflow.validate();
163187
// Serialize it
164-
const definitionTxt = workflowDefinition.serialize();
188+
const definitionTxt = workflow.serialize();
165189
}
166190
catch (ex) {
167191
// Invalid workflow definition
@@ -174,7 +198,7 @@ You can use the fluent API to build a validated and normalized workflow definiti
174198
```typescript
175199
import { documentBuilder, setTaskBuilder, taskListBuilder, workflowBuilder } from '@serverlessworkflow/sdk';
176200

177-
const workflowDefinition = workflowBuilder(/*workflowDefinitionObject*/)
201+
const workflow = workflowBuilder(/*workflowObject*/)
178202
.document(
179203
documentBuilder()
180204
.dsl('1.0.0')
@@ -206,12 +230,12 @@ You can serialize a workflow definition either by using its `serialize` method i
206230
```typescript
207231
import { Classes } from '@serverlessworkflow/sdk';
208232

209-
// const workflowDefinition = <Your preferred method>;
210-
if (workflowDefinition instanceof Classes.Workflow) {
211-
const yaml = workflowDefinition.serialize(/*'yaml' | 'json' */);
233+
// const workflow = <Your preferred method>;
234+
if (workflow instanceof Classes.Workflow) {
235+
const yaml = workflow.serialize(/*'yaml' | 'json' */);
212236
}
213237
else {
214-
const json = Classes.Workflow.serialize(workflowDefinition, 'json');
238+
const json = Classes.Workflow.serialize(workflow, 'json');
215239
}
216240
```
217241
> [!NOTE]
@@ -223,20 +247,114 @@ Validation can be achieved in two ways: via the `validate` function or the insta
223247
```typescript
224248
import { Classes, validate } from '@serverlessworkflow/sdk';
225249

226-
// const workflowDefinition = <Your preferred method>;
250+
const workflow = /* <Your preferred method> */;
227251
try {
228-
if (workflowDefinition instanceof Classes.Workflow) {
229-
workflowDefinition.validate();
252+
if (workflow instanceof Classes.Workflow) {
253+
workflow.validate();
230254
}
231255
else {
232-
validate('Workflow', workflowDefinition);
256+
validate('Workflow', workflow);
233257
}
234258
}
235259
catch (ex) {
236260
// Workflow definition is invalid
237261
}
238262
```
239263

264+
#### Generate a directed graph
265+
A [directed graph](https://en.wikipedia.org/wiki/Directed_graph) of a workflow can be generated using the `buildGraph` function, or alternatives:
266+
- Workflow instance `.toGraph();`
267+
- Static `Classes.Workflow.toGraph(workflow)`
268+
269+
```typescript
270+
import { buildGraph } from '@serverlessworkflow/sdk';
271+
272+
const workflow = {
273+
document: {
274+
dsl: '1.0.0',
275+
name: 'using-plain-object',
276+
version: '1.0.0',
277+
namespace: 'default',
278+
},
279+
do: [
280+
{
281+
step1: {
282+
set: {
283+
variable: 'my first workflow',
284+
},
285+
},
286+
},
287+
],
288+
};
289+
const graph = buildGraph(workflow);
290+
// const workflow = new Classes.Workflow({...}); const graph = workflow.toGraph();
291+
// const graph = Classes.Workflow.toGraph(workflow);
292+
/*{
293+
id: 'root',
294+
type: 'root',
295+
label: undefined,
296+
parent: null,
297+
nodes: [...], // length 3 - root entry node, step1 node, root exit node
298+
edges: [...], // length 2 - entry to step1, step1 to exit
299+
entryNode: {...}, // root entry node
300+
exitNode: {...} // root exit node
301+
}*/
302+
```
303+
304+
#### Generate a MermaidJS flowchart
305+
Generating a [MermaidJS](https://mermaid.js.org/) flowchart can be achieved in two ways: using the `convertToMermaidCode`, the legacy `MermaidDiagram` class, or alternatives:
306+
- Workflow instance `.toMermaidCode();`
307+
- Static `Classes.Workflow.toMermaidCode(workflow)`
308+
309+
```typescript
310+
import { convertToMermaidCode, MermaidDiagram } from '@serverlessworkflow/sdk';
311+
312+
const workflow = {
313+
document: {
314+
dsl: '1.0.0',
315+
name: 'using-plain-object',
316+
version: '1.0.0',
317+
namespace: 'default',
318+
},
319+
do: [
320+
{
321+
step1: {
322+
set: {
323+
variable: 'my first workflow',
324+
},
325+
},
326+
},
327+
],
328+
};
329+
const mermaidCode = convertToMermaidCode(workflow) /* or */;
330+
// const mermaidCode = new MermaidDiagram(workflow).sourceCode();
331+
// const workflow = new Classes.Workflow({...}); const mermaidCode = workflow.toMermaidCode();
332+
// const mermaidCode = Classes.Workflow.toMermaidCode(workflow);
333+
/*
334+
flowchart TD
335+
root-entry-node(( ))
336+
root-exit-node((( )))
337+
/do/0/step1["step1"]
338+
/do/0/step1 --> root-exit-node
339+
root-entry-node --> /do/0/step1
340+
341+
342+
classDef hidden display: none;
343+
*/
344+
```
345+
346+
```mermaid
347+
flowchart TD
348+
root-entry-node(( ))
349+
root-exit-node((( )))
350+
/do/0/step1["step1"]
351+
/do/0/step1 --> root-exit-node
352+
root-entry-node --> /do/0/step1
353+
354+
355+
classDef hidden display: none;
356+
```
357+
240358
### Building Locally
241359

242360
To build the project and run tests locally, use the following commands:

examples/browser/mermaid.html

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<title>Serveless Workflow</title>
7+
<base href="/">
8+
<meta content="width=device-width, initial-scale=1" name="viewport">
9+
</head>
10+
11+
<body>
12+
<p>YAML or JSON:</p>
13+
<textarea id="input" rows="50" cols="100"></textarea>
14+
<div id="diagram-container"></div>
15+
<pre id="output"></pre>
16+
<script src="../../dist/umd/index.umd.js"></script>
17+
<script type="module">
18+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
19+
(async () => {
20+
const { Classes, Specification, validate, convertToMermaidCode } = serverWorkflowSdk;
21+
const workflowDefinition = {
22+
"document": {
23+
"dsl": "1.0.0",
24+
"namespace": "examples",
25+
"name": "accumulate-room-readings",
26+
"version": "0.1.0"
27+
},
28+
"do": [
29+
{
30+
"consumeReading": {
31+
"listen": {
32+
"to": {
33+
"all": [
34+
{
35+
"with": {
36+
"source": "https://my.home.com/sensor",
37+
"type": "my.home.sensors.temperature"
38+
},
39+
"correlate": {
40+
"roomId": {
41+
"from": ".roomid"
42+
}
43+
}
44+
},
45+
{
46+
"with": {
47+
"source": "https://my.home.com/sensor",
48+
"type": "my.home.sensors.humidity"
49+
},
50+
"correlate": {
51+
"roomId": {
52+
"from": ".roomid"
53+
}
54+
}
55+
}
56+
]
57+
}
58+
},
59+
"output": {
60+
"as": ".data.reading"
61+
}
62+
}
63+
},
64+
{
65+
"logReading": {
66+
"for": {
67+
"each": "reading",
68+
"in": ".readings"
69+
},
70+
"do": [
71+
{
72+
"callOrderService": {
73+
"call": "openapi",
74+
"with": {
75+
"document": {
76+
"endpoint": "http://myorg.io/ordersservices.json"
77+
},
78+
"operationId": "logreading"
79+
}
80+
}
81+
}
82+
]
83+
}
84+
},
85+
{
86+
"generateReport": {
87+
"call": "openapi",
88+
"with": {
89+
"document": {
90+
"endpoint": "http://myorg.io/ordersservices.json"
91+
},
92+
"operationId": "produceReport"
93+
}
94+
}
95+
}
96+
],
97+
"timeout": {
98+
"after": {
99+
"hours": 1
100+
}
101+
}
102+
}/* as Specification.Workflow // <-- If you're using TypeScript*/;
103+
const diagramContainerEl = document.getElementById('diagram-container');
104+
const inputTextarea = document.getElementById('input');
105+
const processWorkflow = async () => {
106+
try {
107+
const workflow = Classes.Workflow.deserialize(inputTextarea.value);
108+
const mermaidCode = convertToMermaidCode(workflow);
109+
document.getElementById('output').innerHTML = `--- YAML ---\n${Classes.Workflow.serialize(workflow)}\n\n--- JSON ---\n${Classes.Workflow.serialize(workflow, 'json')}\n\n--- MERMAID ---\n${mermaidCode}`;
110+
mermaid.initialize({ startOnLoad: false });
111+
const { svg, bindFunctions } = await mermaid.render('sw-diagram', mermaidCode);
112+
diagramContainerEl.innerHTML = svg;
113+
}
114+
catch (ex) {
115+
console.error('Invalid workflow', ex);
116+
}
117+
};
118+
let debounceHandle;
119+
inputTextarea.addEventListener('keyup', () => {
120+
if (debounceHandle) {
121+
clearTimeout(debounceHandle);
122+
}
123+
debounceHandle = setTimeout(processWorkflow, 300);
124+
});
125+
inputTextarea.value = JSON.stringify(workflowDefinition, null, 4);
126+
await processWorkflow();
127+
})();
128+
</script>
129+
</body>
130+
131+
</html>

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@serverlessworkflow/sdk",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"schemaVersion": "1.0.0",
55
"description": "Typescript SDK for Serverless Workflow Specification",
66
"main": "umd/index.umd.min.js",

0 commit comments

Comments
 (0)