acts 0.10.4

a fast, tiny, extensiable workflow engine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# Acts workflow engine

[![Build](https://github.com/yaojianpin/acts/actions/workflows/rust.yml/badge.svg)](https://github.com/yaojianpin/acts/actions?workflow=rust)
[![Test](https://github.com/yaojianpin/acts/actions/workflows/test.yml/badge.svg)](https://github.com/yaojianpin/acts/actions?workflow=test)

`acts` is a fast, tiny, extensiable workflow engine, which provides the abilities to execute workflow based on yml model.

The yml workflow model is not as same as the tranditional workflow. such as `bpmn`.  The yml format is inspired by Github actions.  The main point of this workflow is to create a top abstraction to run the workflow logic and interact with the client via `act` node.

This workflow engine focus on the workflow logics itself and message distributions. the complex business logic will be completed by `act` via the act message. 

## Key Features


### Fast

Uses rust to create the lib, there is no virtual machine, no db dependencies. It also provides the feature `store` to enable the local store. 

1. bechmark with memory store
```txt,no_run
load                    time:   [66.438 µs 75.248 µs 84.207 µs]
deploy                  time:   [6.612 µs 17.356 µs 18.282 µs]
start                   time:   [69.952 µs 70.628 µs 71.287 µs]
act                     time:   [7.9698 ms 8.5588 ms 9.0608 ms]
```

### Tiny

The lib size is only 3mb (no store), you can use Adapter to create external store.

### Extensiable

Supports for extending the plugin
Supports for creating external store, please refer to the code under `src/store/db/local`.

## Installation

The easiest way to get the latest version of `acts` is to install it via `cargo`
```bash
cargo add acts
```

## Quickstart

1. Create and start the workflow engine by `engine.new()`.
2. Load a yaml model to create a `workflow`. 
3. Deploy the model in step 2 by `engine.manager()`.
4. Config events by `engine.channel()`.
5. Start the workflow by `engine.executor()`.

```rust,no_run
use acts::{Engine, Vars, Workflow};

#[tokio::main]

async fn main() {
    let engine = Engine::new();

    let text = include_str!("../examples/simple/model.yml");
    let workflow = Workflow::from_yml(text).unwrap();

    let executor = engine.executor();
    engine.manager().deploy(&workflow).expect("fail to deploy workflow");

    let mut vars = Vars::new();
    vars.insert("input".into(), 3.into());
    vars.insert("pid".to_string(), "w1".into());
    executor.start(&workflow.id, &vars).expect("fail to start workflow");;
    let chan = engine.channel();

    chan.on_start(|e| {
        println!("start: {}", e.start_time);
    });

    chan.on_message(|e| {
        println!("message: {:?}", e);
    });

    chan.on_complete(|e| {
        println!("outputs: {:?} end_time: {}", e.outputs, e.end_time);
    });

    chan.on_error(|e| {
        println!("error on proc id: {} model id: {}", e.pid, e.model.id);
    });
}
```

## Examples


Please see [`examples`](<https://github.com/yaojianpin/acts/tree/main/examples>)

## Model Usage


The model is a yaml format file. where there are different type of node, including [`Workflow`], [`Branch`], [`Step`] and [`Act`]. Every workflow can have more steps, a step can have more branches. In a step,  it consists of many acts to complete the step task, such as 'req', 'msg', 'each', 'chain', 'set', 'expose' and so on. these acts are responsible to act with client or do a single task simplely.

The `run` property is the script based on `javascript`
The `inputs` property can be set the initialzed vars in each node.

```yml
name: model name
inputs:
  value: 0
steps:
  - name: step 1
    run: |
      print("step 1")

  - name: step 2
    branches:
      - name: branch 1
        if: ${ $("value") > 100 }
        run: |
            print("branch 1");

      - name: branch 2
        if: ${ $("value") <= 100 }
        steps:
            - name: step 3
              run: |
                print("branch 2")      
```

### Inputs

In the [`Workflow`], you can set the `inputs` to init the workflow vars. 

```yml
name: model name
inputs:
  a: 100
steps:
  - name: step1
    run: |
      env.set("output_key", "output value");
```

The inputs can also be set by starting the workflow.

```rust,no_run
use acts::{Engine, Vars, Workflow};

#[tokio::main]

async fn main() {
  let engine = Engine::new();
  let executor = engine.executor();

  let mut vars = Vars::new();
  vars.insert("input".into(), 3.into());
  vars.insert("pid".to_string(), "w2".into());

  executor.start("m1", &vars);
}
```

### Outputs


In the [`Workflow`], you can set the `outputs` to output the env to use.
```yml
name: model name
outputs:
  output_key:
steps:
  - name: step1
    run: |
      env.set("output_key", "output value");
```

### Setup

In `workflow` node, you can setup acts by `setup`.

The act `msg` is to send a message to client. 
For more acts, please see the comments as follow:

```yml
name: model name
setup:
setup:
  # set the data by !set
  - !set
    a: ["u1", "u2"]
    v: 10

  # checks the condition and enters into the 'then' acts
  - !if
    on: $("v") > 0
    then:
      - !msg
        id: msg2
  # on step created
  - !on_created
    - !msg
      id: msg3

  # on workflow completed
  - !on_completed
    - !msg
      id: msg4
  # on act created
  - !on_before_update
    - !msg
      id: msg5
  # on act completed
  - !on_updated
    - !msg
      id: msg5

  # on step created or completed
  - !on_step
      - !msg
        id: msg3
  # on error catch
  - !on_error_catch
    - err: err1
      then:
        - !req
          id: act3
  # expose the data with special keys
  - !expose
      out:
```


### Steps

Use `steps` to add step to the workflow
```yml
name: model name
steps:
  - id: step1
    name: step 1
  - id: step2
    name: step 2
```

#### step.setup
Use the `setup` to setup some acts when the step is creating.

The acts are 'req', 'msg', 'set', 'expose', 'chain', 'each' and 'if',  it also includes some hooks, such as 'on_created', 'on_completed', 'on_before_update', 'on_updated', 'on_timeout' and 'on_error_catch'.

```yml
name: a setup example
id: setup
steps:
  - name: step 1
    id: step1
    setup:
  
      # set the data by !set
      - !set
        a: ["u1", "u2"]
        v: 10
      # send message with key msg1
      - !msg
        id: msg1
        inputs:
          data: ${ $("a") }

      # chains and runs 'run' one by one by 'in' data
      - !chain
        in: $("a")
        run:
          - !req
            id: act1

      # each the var 'a'
      - !each
        in: $("a")
        run:
          # the each will generate two !req with `act_index`  and `act_value`
          # the `act_index` is the each index. It is 0 and 1 in this example
          # the `act_value` is the each data. It is 'u1' and 'u2' in this example
          - !req
            id: act2
      # checks the condition and enters into the 'then' acts
      - !if
        on: $("v") > 0
        then:
          - !msg
            id: msg2
      # on step created
      - !on_created
        - !msg
          id: msg3

      # on step completed
      - !on_completed
        - !msg
          id: msg4
      # on act created
      - !on_before_update
        - !msg
          id: msg5
      # on act completed
      - !on_updated
        - !msg
          id: msg5

      # on step created or completed
      - !on_step
          - !msg
            id: msg3
      # on error catch
      - !on_error_catch
        - err: err1
          then:
            - !req
              id: act3
      # on timeout 
      - !on_timeout
        - on: 6h
          then:
            - !req
              id: act3
      # expose the data with special keys
      - !expose
         out:
  - name: final
    id: final
```

For more acts example, please see [`examples`](<https://github.com/yaojianpin/acts/tree/main/examples>)

#### step.catches

Use the `catches` to capture the `step` error.
```yml
name: a catches example
id: catches
steps:
  - name: prepare
    id: prepare
    acts:
      - !req
        id: init
  - name: step1
    id: step1
    acts:
      - !req
        id: act1
    # catch the step errors
    catches:
      - id: catch1
        err: err1
        then:
          - !req
            id: act2
      - id: catch2
        err: err2
        then:
          - !req
            id: act3
      - id: catch_others

  - name: final
    id: final
```

#### step.timeout
Use the `timeout` to check the task time.
```yml
name: a timeout example
id: timeout
steps:
  - name: prepare
    id: prepare
    acts:
      - !req
        id: init
  - name: step1
    id: step1
    acts:
      - !req
        id: act1
    # check timeout rules
    timeout:
      # 1d means one day
      # triggers act2 when timeout
      - on: 1d
        then:
          - !req
            id: act2
      # 2h means two hours
      # triggers act3 when timeout
      - on: 2h
        then:
          - !req
            id: act3

  - name: final
    id: final
```

### Branches

Use `branches` to add branch to the step
```yml
name: model name
steps:
  - id: step1
    name: step 1
    branches:
      - id: b1
        if: $("v") > 0
        steps: 
          - name: step a
          - name: step b
      - id: b2
        else: true
        steps:
          - name: step c
          - name: step d
  - id: step2
    name: step 2

```

### Acts

Use `acts` to create act to interact with client, or finish a special function through several act type.

```yml
name: model name
outputs:
  output_key:
steps:
  - name: step1
    acts:
      # send message to client
      - !msg
        id: msg1
        inputs:
          a: 1
          
      # req is a act to send a request from acts server
      # the client can complete the act and pass data to serever
      - !req
        id: init
        name: my act init

        # passes data to the act
        inputs:
          a: 6
        
        # exposes the data to step
        outputs:
          a:

        # limits the data keys when acting
        rets:
          a:
```

For more acts example, please see [`examples`](<https://github.com/yaojianpin/acts/tree/main/examples>)

## Store
You can enable the store feature using `store`, which uses [`duckdb`](<https://github.com/duckdb/duckdb>) to build.

To enable feature `store`
```ignore
[dependencies]
acts = { version = "*", features = ["store"] }
```

For external store:

 ```rust,no_run
 use acts::{Engine, Builder, data::{Model, Proc, Task, Package, Message}, DbSet, StoreAdapter};
 use std::sync::Arc;

 #[derive(Clone)]
 struct TestStore;

 impl StoreAdapter for TestStore {
     fn models(&self) -> Arc<dyn DbSet<Item = Model>> {
         todo!()
     }
     fn procs(&self) -> Arc<dyn DbSet<Item =Proc>> {
         todo!()
     }
     fn tasks(&self) -> Arc<dyn DbSet<Item =Task>> {
         todo!()
     }
     fn packages(&self) -> Arc<dyn DbSet<Item =Package>> {
         todo!()
     }
     fn messages(&self) -> Arc<dyn DbSet<Item =Message>> {
         todo!()
     }
     fn init(&self) {}
     fn close(&self) {}
 }

#[tokio::main]

async fn main() {
    // set custom store
  let store = TestStore;
  let engine = Builder::new().store(&store).build();
}
 ```

## Package

`acts` engine intergrates the [`rquickjs`](<https://github.com/delskayn/rquickjs>) runtime to execute the package, which can extend the engine abilities.
for more information please see the example [`package`](<https://github.com/yaojianpin/acts/tree/main/examples/package>)

## Acts-Server

Create a acts-server to interact with clients based on grpc.
please see more from [`acts-server`](<https://github.com/yaojianpin/acts-server>)

## Acts-Channel

The channel is used to interact with the server. the actions includes 'deploy', 'start', 'push', 'remove', 'complete', 'back', 'cancel', 'skip', 'abort' and 'error'.

please see more from [`acts-channel`](<https://github.com/yaojianpin/acts-channel>)