@hotmeshio/hotmesh 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -0
- package/README.md +158 -38
- package/build/index.d.ts +1 -3
- package/build/index.js +1 -5
- package/build/modules/utils.js +3 -31
- package/build/package.json +63 -79
- package/build/services/activities/activity.d.ts +97 -9
- package/build/services/activities/activity.js +323 -86
- package/build/services/activities/await.d.ts +101 -0
- package/build/services/activities/await.js +103 -2
- package/build/services/activities/cycle.d.ts +82 -0
- package/build/services/activities/cycle.js +86 -8
- package/build/services/activities/hook.d.ts +144 -1
- package/build/services/activities/hook.js +162 -21
- package/build/services/activities/interrupt.d.ts +112 -0
- package/build/services/activities/interrupt.js +134 -29
- package/build/services/activities/signal.d.ts +111 -4
- package/build/services/activities/signal.js +136 -28
- package/build/services/activities/trigger.d.ts +56 -4
- package/build/services/activities/trigger.js +119 -35
- package/build/services/activities/worker.d.ts +107 -0
- package/build/services/activities/worker.js +109 -2
- package/build/services/collator/index.d.ts +116 -30
- package/build/services/collator/index.js +211 -115
- package/build/services/connector/factory.d.ts +1 -1
- package/build/services/connector/factory.js +1 -11
- package/build/services/engine/index.d.ts +22 -6
- package/build/services/engine/index.js +49 -18
- package/build/services/exporter/index.d.ts +2 -0
- package/build/services/exporter/index.js +1 -0
- package/build/services/hotmesh/index.d.ts +471 -236
- package/build/services/hotmesh/index.js +473 -238
- package/build/services/memflow/client.js +2 -2
- package/build/services/memflow/handle.js +1 -1
- package/build/services/memflow/index.d.ts +1 -1
- package/build/services/memflow/index.js +1 -1
- package/build/services/memflow/workflow/all.d.ts +28 -3
- package/build/services/memflow/workflow/all.js +28 -3
- package/build/services/memflow/workflow/context.d.ts +44 -1
- package/build/services/memflow/workflow/context.js +44 -1
- package/build/services/memflow/workflow/didRun.d.ts +23 -3
- package/build/services/memflow/workflow/didRun.js +23 -3
- package/build/services/memflow/workflow/emit.d.ts +43 -4
- package/build/services/memflow/workflow/emit.js +43 -4
- package/build/services/memflow/workflow/enrich.d.ts +32 -4
- package/build/services/memflow/workflow/enrich.js +32 -4
- package/build/services/memflow/workflow/entityMethods.d.ts +54 -7
- package/build/services/memflow/workflow/entityMethods.js +54 -7
- package/build/services/memflow/workflow/execChild.d.ts +96 -8
- package/build/services/memflow/workflow/execChild.js +96 -8
- package/build/services/memflow/workflow/execHook.d.ts +54 -39
- package/build/services/memflow/workflow/execHook.js +52 -38
- package/build/services/memflow/workflow/execHookBatch.d.ts +82 -29
- package/build/services/memflow/workflow/execHookBatch.js +80 -28
- package/build/services/memflow/workflow/hook.d.ts +68 -3
- package/build/services/memflow/workflow/hook.js +69 -4
- package/build/services/memflow/workflow/index.d.ts +65 -10
- package/build/services/memflow/workflow/index.js +65 -10
- package/build/services/memflow/workflow/interrupt.d.ts +50 -4
- package/build/services/memflow/workflow/interrupt.js +50 -4
- package/build/services/memflow/workflow/interruption.d.ts +49 -16
- package/build/services/memflow/workflow/interruption.js +49 -16
- package/build/services/memflow/workflow/isSideEffectAllowed.d.ts +21 -4
- package/build/services/memflow/workflow/isSideEffectAllowed.js +21 -4
- package/build/services/memflow/workflow/proxyActivities.d.ts +70 -42
- package/build/services/memflow/workflow/proxyActivities.js +70 -42
- package/build/services/memflow/workflow/random.d.ts +33 -3
- package/build/services/memflow/workflow/random.js +33 -3
- package/build/services/memflow/workflow/searchMethods.d.ts +49 -2
- package/build/services/memflow/workflow/searchMethods.js +49 -2
- package/build/services/memflow/workflow/signal.d.ts +51 -22
- package/build/services/memflow/workflow/signal.js +52 -23
- package/build/services/memflow/workflow/sleepFor.d.ts +57 -18
- package/build/services/memflow/workflow/sleepFor.js +57 -18
- package/build/services/memflow/workflow/trace.d.ts +39 -6
- package/build/services/memflow/workflow/trace.js +39 -6
- package/build/services/memflow/workflow/waitFor.d.ts +55 -18
- package/build/services/memflow/workflow/waitFor.js +55 -18
- package/build/services/router/consumption/index.js +1 -1
- package/build/services/search/factory.js +1 -9
- package/build/services/store/factory.js +1 -9
- package/build/services/store/index.d.ts +6 -1
- package/build/services/store/providers/postgres/kvsql.d.ts +4 -0
- package/build/services/store/providers/postgres/kvsql.js +4 -0
- package/build/services/store/providers/postgres/kvtransaction.d.ts +2 -0
- package/build/services/store/providers/postgres/kvtransaction.js +23 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +51 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +193 -1
- package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +4 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.js +6 -0
- package/build/services/store/providers/postgres/postgres.d.ts +21 -1
- package/build/services/store/providers/postgres/postgres.js +42 -4
- package/build/services/stream/factory.js +1 -17
- package/build/services/stream/providers/postgres/scout.js +2 -2
- package/build/services/sub/factory.js +1 -9
- package/build/services/sub/index.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.js +25 -10
- package/build/services/task/index.d.ts +1 -1
- package/build/services/task/index.js +2 -6
- package/build/services/telemetry/index.js +6 -0
- package/build/types/activity.d.ts +1 -1
- package/build/types/hotmesh.d.ts +1 -1
- package/build/types/index.d.ts +0 -1
- package/build/types/index.js +1 -4
- package/build/types/job.d.ts +1 -1
- package/build/types/memflow.d.ts +1 -1
- package/build/types/provider.d.ts +1 -1
- package/build/types/quorum.d.ts +2 -2
- package/build/vitest.config.d.ts +2 -0
- package/build/vitest.config.js +18 -0
- package/index.ts +0 -4
- package/package.json +63 -79
- package/vitest.config.ts +17 -0
- package/build/services/connector/providers/ioredis.d.ts +0 -9
- package/build/services/connector/providers/ioredis.js +0 -26
- package/build/services/connector/providers/redis.d.ts +0 -9
- package/build/services/connector/providers/redis.js +0 -38
- package/build/services/search/providers/redis/ioredis.d.ts +0 -23
- package/build/services/search/providers/redis/ioredis.js +0 -189
- package/build/services/search/providers/redis/redis.d.ts +0 -23
- package/build/services/search/providers/redis/redis.js +0 -202
- package/build/services/store/providers/redis/_base.d.ts +0 -137
- package/build/services/store/providers/redis/_base.js +0 -980
- package/build/services/store/providers/redis/ioredis.d.ts +0 -20
- package/build/services/store/providers/redis/ioredis.js +0 -190
- package/build/services/store/providers/redis/redis.d.ts +0 -18
- package/build/services/store/providers/redis/redis.js +0 -199
- package/build/services/stream/providers/redis/ioredis.d.ts +0 -61
- package/build/services/stream/providers/redis/ioredis.js +0 -272
- package/build/services/stream/providers/redis/redis.d.ts +0 -61
- package/build/services/stream/providers/redis/redis.js +0 -305
- package/build/services/sub/providers/redis/ioredis.d.ts +0 -20
- package/build/services/sub/providers/redis/ioredis.js +0 -161
- package/build/services/sub/providers/redis/redis.d.ts +0 -18
- package/build/services/sub/providers/redis/redis.js +0 -148
- package/build/types/redis.d.ts +0 -258
- package/build/types/redis.js +0 -11
|
@@ -3,6 +3,107 @@ import { ActivityData, ActivityMetadata, AwaitActivity, ActivityType } from '../
|
|
|
3
3
|
import { ProviderTransaction } from '../../types/provider';
|
|
4
4
|
import { JobState } from '../../types/job';
|
|
5
5
|
import { Activity } from './activity';
|
|
6
|
+
/**
|
|
7
|
+
* Invokes another graph (sub-flow) and optionally waits for its completion.
|
|
8
|
+
* The `await` activity enables compositional workflows where one graph
|
|
9
|
+
* triggers another by publishing to its `subscribes` topic, creating a
|
|
10
|
+
* parent-child relationship between flows.
|
|
11
|
+
*
|
|
12
|
+
* ## YAML Configuration
|
|
13
|
+
*
|
|
14
|
+
* The `topic` in the await activity must match the `subscribes` topic of
|
|
15
|
+
* the child graph. Both graphs are defined in the same app YAML:
|
|
16
|
+
*
|
|
17
|
+
* ```yaml
|
|
18
|
+
* app:
|
|
19
|
+
* id: myapp
|
|
20
|
+
* version: '1'
|
|
21
|
+
* graphs:
|
|
22
|
+
*
|
|
23
|
+
* # ── Parent graph ──────────────────────────────
|
|
24
|
+
* - subscribes: order.placed
|
|
25
|
+
* expire: 120
|
|
26
|
+
*
|
|
27
|
+
* activities:
|
|
28
|
+
* t1:
|
|
29
|
+
* type: trigger
|
|
30
|
+
* job:
|
|
31
|
+
* maps:
|
|
32
|
+
* orderId: '{$self.output.data.id}'
|
|
33
|
+
*
|
|
34
|
+
* a1:
|
|
35
|
+
* type: await
|
|
36
|
+
* topic: approval.requested # ◄── targets the child graph's subscribes
|
|
37
|
+
* await: true
|
|
38
|
+
* input:
|
|
39
|
+
* schema:
|
|
40
|
+
* type: object
|
|
41
|
+
* properties:
|
|
42
|
+
* orderId: { type: string }
|
|
43
|
+
* maps:
|
|
44
|
+
* orderId: '{t1.output.data.id}'
|
|
45
|
+
* output:
|
|
46
|
+
* schema:
|
|
47
|
+
* type: object
|
|
48
|
+
* properties:
|
|
49
|
+
* approved: { type: boolean }
|
|
50
|
+
* job:
|
|
51
|
+
* maps:
|
|
52
|
+
* approval: '{$self.output.data.approved}'
|
|
53
|
+
*
|
|
54
|
+
* done:
|
|
55
|
+
* type: hook
|
|
56
|
+
*
|
|
57
|
+
* transitions:
|
|
58
|
+
* t1:
|
|
59
|
+
* - to: a1
|
|
60
|
+
* a1:
|
|
61
|
+
* - to: done
|
|
62
|
+
*
|
|
63
|
+
* # ── Child graph (invoked by the await) ────────
|
|
64
|
+
* - subscribes: approval.requested # ◄── matched by the await activity's topic
|
|
65
|
+
* publishes: approval.completed
|
|
66
|
+
* expire: 60
|
|
67
|
+
*
|
|
68
|
+
* activities:
|
|
69
|
+
* t1:
|
|
70
|
+
* type: trigger
|
|
71
|
+
* review:
|
|
72
|
+
* type: worker
|
|
73
|
+
* topic: approval.review
|
|
74
|
+
*
|
|
75
|
+
* transitions:
|
|
76
|
+
* t1:
|
|
77
|
+
* - to: review
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* ## Fire-and-Forget Mode
|
|
81
|
+
*
|
|
82
|
+
* When `await` is explicitly set to `false`, the activity starts the child
|
|
83
|
+
* flow but does not wait for its completion. The parent flow immediately
|
|
84
|
+
* continues. The child's `job_id` is returned as the output.
|
|
85
|
+
*
|
|
86
|
+
* ```yaml
|
|
87
|
+
* a1:
|
|
88
|
+
* type: await
|
|
89
|
+
* topic: background.process
|
|
90
|
+
* await: false
|
|
91
|
+
* job:
|
|
92
|
+
* maps:
|
|
93
|
+
* childJobId: '{$self.output.data.job_id}'
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* ## Execution Model
|
|
97
|
+
*
|
|
98
|
+
* Await is a **Category A (duplex)** activity:
|
|
99
|
+
* - **Leg 1** (`process`): Maps input data and publishes a
|
|
100
|
+
* `StreamDataType.AWAIT` message to the engine stream. The engine
|
|
101
|
+
* starts the child flow.
|
|
102
|
+
* - **Leg 2** (`processEvent`, inherited): Receives the child flow's
|
|
103
|
+
* final output, maps output data, and transitions to adjacent activities.
|
|
104
|
+
*
|
|
105
|
+
* @see {@link AwaitActivity} for the TypeScript interface
|
|
106
|
+
*/
|
|
6
107
|
declare class Await extends Activity {
|
|
7
108
|
config: AwaitActivity;
|
|
8
109
|
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
@@ -8,6 +8,107 @@ const pipe_1 = require("../pipe");
|
|
|
8
8
|
const telemetry_1 = require("../telemetry");
|
|
9
9
|
const stream_1 = require("../../types/stream");
|
|
10
10
|
const activity_1 = require("./activity");
|
|
11
|
+
/**
|
|
12
|
+
* Invokes another graph (sub-flow) and optionally waits for its completion.
|
|
13
|
+
* The `await` activity enables compositional workflows where one graph
|
|
14
|
+
* triggers another by publishing to its `subscribes` topic, creating a
|
|
15
|
+
* parent-child relationship between flows.
|
|
16
|
+
*
|
|
17
|
+
* ## YAML Configuration
|
|
18
|
+
*
|
|
19
|
+
* The `topic` in the await activity must match the `subscribes` topic of
|
|
20
|
+
* the child graph. Both graphs are defined in the same app YAML:
|
|
21
|
+
*
|
|
22
|
+
* ```yaml
|
|
23
|
+
* app:
|
|
24
|
+
* id: myapp
|
|
25
|
+
* version: '1'
|
|
26
|
+
* graphs:
|
|
27
|
+
*
|
|
28
|
+
* # ── Parent graph ──────────────────────────────
|
|
29
|
+
* - subscribes: order.placed
|
|
30
|
+
* expire: 120
|
|
31
|
+
*
|
|
32
|
+
* activities:
|
|
33
|
+
* t1:
|
|
34
|
+
* type: trigger
|
|
35
|
+
* job:
|
|
36
|
+
* maps:
|
|
37
|
+
* orderId: '{$self.output.data.id}'
|
|
38
|
+
*
|
|
39
|
+
* a1:
|
|
40
|
+
* type: await
|
|
41
|
+
* topic: approval.requested # ◄── targets the child graph's subscribes
|
|
42
|
+
* await: true
|
|
43
|
+
* input:
|
|
44
|
+
* schema:
|
|
45
|
+
* type: object
|
|
46
|
+
* properties:
|
|
47
|
+
* orderId: { type: string }
|
|
48
|
+
* maps:
|
|
49
|
+
* orderId: '{t1.output.data.id}'
|
|
50
|
+
* output:
|
|
51
|
+
* schema:
|
|
52
|
+
* type: object
|
|
53
|
+
* properties:
|
|
54
|
+
* approved: { type: boolean }
|
|
55
|
+
* job:
|
|
56
|
+
* maps:
|
|
57
|
+
* approval: '{$self.output.data.approved}'
|
|
58
|
+
*
|
|
59
|
+
* done:
|
|
60
|
+
* type: hook
|
|
61
|
+
*
|
|
62
|
+
* transitions:
|
|
63
|
+
* t1:
|
|
64
|
+
* - to: a1
|
|
65
|
+
* a1:
|
|
66
|
+
* - to: done
|
|
67
|
+
*
|
|
68
|
+
* # ── Child graph (invoked by the await) ────────
|
|
69
|
+
* - subscribes: approval.requested # ◄── matched by the await activity's topic
|
|
70
|
+
* publishes: approval.completed
|
|
71
|
+
* expire: 60
|
|
72
|
+
*
|
|
73
|
+
* activities:
|
|
74
|
+
* t1:
|
|
75
|
+
* type: trigger
|
|
76
|
+
* review:
|
|
77
|
+
* type: worker
|
|
78
|
+
* topic: approval.review
|
|
79
|
+
*
|
|
80
|
+
* transitions:
|
|
81
|
+
* t1:
|
|
82
|
+
* - to: review
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* ## Fire-and-Forget Mode
|
|
86
|
+
*
|
|
87
|
+
* When `await` is explicitly set to `false`, the activity starts the child
|
|
88
|
+
* flow but does not wait for its completion. The parent flow immediately
|
|
89
|
+
* continues. The child's `job_id` is returned as the output.
|
|
90
|
+
*
|
|
91
|
+
* ```yaml
|
|
92
|
+
* a1:
|
|
93
|
+
* type: await
|
|
94
|
+
* topic: background.process
|
|
95
|
+
* await: false
|
|
96
|
+
* job:
|
|
97
|
+
* maps:
|
|
98
|
+
* childJobId: '{$self.output.data.job_id}'
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* ## Execution Model
|
|
102
|
+
*
|
|
103
|
+
* Await is a **Category A (duplex)** activity:
|
|
104
|
+
* - **Leg 1** (`process`): Maps input data and publishes a
|
|
105
|
+
* `StreamDataType.AWAIT` message to the engine stream. The engine
|
|
106
|
+
* starts the child flow.
|
|
107
|
+
* - **Leg 2** (`processEvent`, inherited): Receives the child flow's
|
|
108
|
+
* final output, maps output data, and transitions to adjacent activities.
|
|
109
|
+
*
|
|
110
|
+
* @see {@link AwaitActivity} for the TypeScript interface
|
|
111
|
+
*/
|
|
11
112
|
class Await extends activity_1.Activity {
|
|
12
113
|
constructor(config, data, metadata, hook, engine, context) {
|
|
13
114
|
super(config, data, metadata, hook, engine, context);
|
|
@@ -25,11 +126,11 @@ class Await extends activity_1.Activity {
|
|
|
25
126
|
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
26
127
|
telemetry.startActivitySpan(this.leg);
|
|
27
128
|
this.mapInputData();
|
|
28
|
-
//save state and
|
|
129
|
+
//save state and mark Leg1 complete
|
|
29
130
|
const transaction = this.store.transact();
|
|
30
131
|
//todo: await this.registerTimeout();
|
|
31
132
|
const messageId = await this.execActivity(transaction);
|
|
32
|
-
await collator_1.CollatorService.
|
|
133
|
+
await collator_1.CollatorService.notarizeLeg1Completion(this, transaction);
|
|
33
134
|
await this.setState(transaction);
|
|
34
135
|
await this.setStatus(0, transaction);
|
|
35
136
|
const multiResponse = (await transaction.exec());
|
|
@@ -3,6 +3,88 @@ import { ActivityData, ActivityMetadata, ActivityType, CycleActivity } from '../
|
|
|
3
3
|
import { ProviderTransaction } from '../../types/provider';
|
|
4
4
|
import { JobState } from '../../types/job';
|
|
5
5
|
import { Activity } from './activity';
|
|
6
|
+
/**
|
|
7
|
+
* Re-executes an ancestor activity in a new dimensional thread, enabling
|
|
8
|
+
* retry loops and iterative patterns without violating the DAG constraint.
|
|
9
|
+
* The `cycle` activity targets a specific ancestor (typically a
|
|
10
|
+
* `Hook` with `cycle: true`) and sends execution back to that point.
|
|
11
|
+
*
|
|
12
|
+
* Each cycle iteration runs in a fresh **dimensional thread** — individual
|
|
13
|
+
* activity state is isolated per iteration, while **shared job state**
|
|
14
|
+
* (`job.maps`) accumulates across iterations. This pattern enables retries,
|
|
15
|
+
* polling loops, and iterative processing.
|
|
16
|
+
*
|
|
17
|
+
* ## YAML Configuration
|
|
18
|
+
*
|
|
19
|
+
* ```yaml
|
|
20
|
+
* app:
|
|
21
|
+
* id: myapp
|
|
22
|
+
* version: '1'
|
|
23
|
+
* graphs:
|
|
24
|
+
* - subscribes: retry.start
|
|
25
|
+
* expire: 300
|
|
26
|
+
*
|
|
27
|
+
* activities:
|
|
28
|
+
* t1:
|
|
29
|
+
* type: trigger
|
|
30
|
+
*
|
|
31
|
+
* pivot:
|
|
32
|
+
* type: hook
|
|
33
|
+
* cycle: true # marks this activity as a cycle target
|
|
34
|
+
* output:
|
|
35
|
+
* maps:
|
|
36
|
+
* retryCount: 0
|
|
37
|
+
*
|
|
38
|
+
* do_work:
|
|
39
|
+
* type: worker
|
|
40
|
+
* topic: work.do
|
|
41
|
+
* output:
|
|
42
|
+
* schema:
|
|
43
|
+
* type: object
|
|
44
|
+
* properties:
|
|
45
|
+
* result: { type: string }
|
|
46
|
+
*
|
|
47
|
+
* retry:
|
|
48
|
+
* type: cycle
|
|
49
|
+
* ancestor: pivot # re-execute from this activity
|
|
50
|
+
* input:
|
|
51
|
+
* maps:
|
|
52
|
+
* retryCount: # increment retry counter each cycle
|
|
53
|
+
* '@pipe':
|
|
54
|
+
* - ['{pivot.output.data.retryCount}', 1]
|
|
55
|
+
* - ['{@math.add}']
|
|
56
|
+
*
|
|
57
|
+
* done:
|
|
58
|
+
* type: hook
|
|
59
|
+
*
|
|
60
|
+
* transitions:
|
|
61
|
+
* t1:
|
|
62
|
+
* - to: pivot
|
|
63
|
+
* pivot:
|
|
64
|
+
* - to: do_work
|
|
65
|
+
* do_work:
|
|
66
|
+
* - to: retry
|
|
67
|
+
* conditions:
|
|
68
|
+
* code: 500 # cycle on error
|
|
69
|
+
* - to: done
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* ## Key Behaviors
|
|
73
|
+
*
|
|
74
|
+
* - The `ancestor` field must reference an activity with `cycle: true`.
|
|
75
|
+
* - The cycle activity's `input.maps` override the ancestor's output data
|
|
76
|
+
* for the next iteration, allowing each cycle to pass different values.
|
|
77
|
+
* - Dimensional isolation ensures parallel cycle iterations don't collide.
|
|
78
|
+
*
|
|
79
|
+
* ## Execution Model
|
|
80
|
+
*
|
|
81
|
+
* Cycle is a **Category A (Leg 1 only)** activity:
|
|
82
|
+
* - Maps input data, resolves the re-entry dimensional address, and
|
|
83
|
+
* publishes a stream message addressed to the ancestor activity.
|
|
84
|
+
* - The ancestor re-enters via its Leg 2 path in the new dimension.
|
|
85
|
+
*
|
|
86
|
+
* @see {@link CycleActivity} for the TypeScript interface
|
|
87
|
+
*/
|
|
6
88
|
declare class Cycle extends Activity {
|
|
7
89
|
config: CycleActivity;
|
|
8
90
|
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
@@ -6,6 +6,88 @@ const utils_1 = require("../../modules/utils");
|
|
|
6
6
|
const collator_1 = require("../collator");
|
|
7
7
|
const telemetry_1 = require("../telemetry");
|
|
8
8
|
const activity_1 = require("./activity");
|
|
9
|
+
/**
|
|
10
|
+
* Re-executes an ancestor activity in a new dimensional thread, enabling
|
|
11
|
+
* retry loops and iterative patterns without violating the DAG constraint.
|
|
12
|
+
* The `cycle` activity targets a specific ancestor (typically a
|
|
13
|
+
* `Hook` with `cycle: true`) and sends execution back to that point.
|
|
14
|
+
*
|
|
15
|
+
* Each cycle iteration runs in a fresh **dimensional thread** — individual
|
|
16
|
+
* activity state is isolated per iteration, while **shared job state**
|
|
17
|
+
* (`job.maps`) accumulates across iterations. This pattern enables retries,
|
|
18
|
+
* polling loops, and iterative processing.
|
|
19
|
+
*
|
|
20
|
+
* ## YAML Configuration
|
|
21
|
+
*
|
|
22
|
+
* ```yaml
|
|
23
|
+
* app:
|
|
24
|
+
* id: myapp
|
|
25
|
+
* version: '1'
|
|
26
|
+
* graphs:
|
|
27
|
+
* - subscribes: retry.start
|
|
28
|
+
* expire: 300
|
|
29
|
+
*
|
|
30
|
+
* activities:
|
|
31
|
+
* t1:
|
|
32
|
+
* type: trigger
|
|
33
|
+
*
|
|
34
|
+
* pivot:
|
|
35
|
+
* type: hook
|
|
36
|
+
* cycle: true # marks this activity as a cycle target
|
|
37
|
+
* output:
|
|
38
|
+
* maps:
|
|
39
|
+
* retryCount: 0
|
|
40
|
+
*
|
|
41
|
+
* do_work:
|
|
42
|
+
* type: worker
|
|
43
|
+
* topic: work.do
|
|
44
|
+
* output:
|
|
45
|
+
* schema:
|
|
46
|
+
* type: object
|
|
47
|
+
* properties:
|
|
48
|
+
* result: { type: string }
|
|
49
|
+
*
|
|
50
|
+
* retry:
|
|
51
|
+
* type: cycle
|
|
52
|
+
* ancestor: pivot # re-execute from this activity
|
|
53
|
+
* input:
|
|
54
|
+
* maps:
|
|
55
|
+
* retryCount: # increment retry counter each cycle
|
|
56
|
+
* '@pipe':
|
|
57
|
+
* - ['{pivot.output.data.retryCount}', 1]
|
|
58
|
+
* - ['{@math.add}']
|
|
59
|
+
*
|
|
60
|
+
* done:
|
|
61
|
+
* type: hook
|
|
62
|
+
*
|
|
63
|
+
* transitions:
|
|
64
|
+
* t1:
|
|
65
|
+
* - to: pivot
|
|
66
|
+
* pivot:
|
|
67
|
+
* - to: do_work
|
|
68
|
+
* do_work:
|
|
69
|
+
* - to: retry
|
|
70
|
+
* conditions:
|
|
71
|
+
* code: 500 # cycle on error
|
|
72
|
+
* - to: done
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* ## Key Behaviors
|
|
76
|
+
*
|
|
77
|
+
* - The `ancestor` field must reference an activity with `cycle: true`.
|
|
78
|
+
* - The cycle activity's `input.maps` override the ancestor's output data
|
|
79
|
+
* for the next iteration, allowing each cycle to pass different values.
|
|
80
|
+
* - Dimensional isolation ensures parallel cycle iterations don't collide.
|
|
81
|
+
*
|
|
82
|
+
* ## Execution Model
|
|
83
|
+
*
|
|
84
|
+
* Cycle is a **Category A (Leg 1 only)** activity:
|
|
85
|
+
* - Maps input data, resolves the re-entry dimensional address, and
|
|
86
|
+
* publishes a stream message addressed to the ancestor activity.
|
|
87
|
+
* - The ancestor re-enters via its Leg 2 path in the new dimension.
|
|
88
|
+
*
|
|
89
|
+
* @see {@link CycleActivity} for the TypeScript interface
|
|
90
|
+
*/
|
|
9
91
|
class Cycle extends activity_1.Activity {
|
|
10
92
|
constructor(config, data, metadata, hook, engine, context) {
|
|
11
93
|
super(config, data, metadata, hook, engine, context);
|
|
@@ -23,23 +105,19 @@ class Cycle extends activity_1.Activity {
|
|
|
23
105
|
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
24
106
|
telemetry.startActivitySpan(this.leg);
|
|
25
107
|
this.mapInputData();
|
|
26
|
-
//set state/status
|
|
27
|
-
|
|
108
|
+
//set state/status, cycle ancestor, and mark Leg1 complete — single transaction
|
|
109
|
+
const transaction = this.store.transact();
|
|
28
110
|
await this.setState(transaction);
|
|
29
111
|
await this.setStatus(0, transaction); //leg 1 never changes job status
|
|
112
|
+
const messageId = await this.cycleAncestorActivity(transaction);
|
|
113
|
+
await collator_1.CollatorService.notarizeLeg1Completion(this, transaction);
|
|
30
114
|
const txResponse = (await transaction.exec());
|
|
31
115
|
telemetry.mapActivityAttributes();
|
|
32
116
|
const jobStatus = this.resolveStatus(txResponse);
|
|
33
|
-
//cycle the target ancestor
|
|
34
|
-
transaction = this.store.transact();
|
|
35
|
-
const messageId = await this.cycleAncestorActivity(transaction);
|
|
36
117
|
telemetry.setActivityAttributes({
|
|
37
118
|
'app.activity.mid': messageId,
|
|
38
119
|
'app.job.jss': jobStatus,
|
|
39
120
|
});
|
|
40
|
-
//exit early (`Cycle` activities only execute Leg 1)
|
|
41
|
-
await collator_1.CollatorService.notarizeEarlyExit(this, transaction);
|
|
42
|
-
(await transaction.exec());
|
|
43
121
|
return this.context.metadata.aid;
|
|
44
122
|
}
|
|
45
123
|
catch (error) {
|
|
@@ -7,12 +7,155 @@ import { ProviderTransaction } from '../../types/provider';
|
|
|
7
7
|
import { StreamCode, StreamStatus } from '../../types/stream';
|
|
8
8
|
import { Activity } from './activity';
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* A versatile pause/resume activity that supports three distinct patterns:
|
|
11
|
+
* **time hook** (sleep), **web hook** (external signal), and **passthrough**
|
|
12
|
+
* (immediate transition with optional data mapping).
|
|
13
|
+
*
|
|
14
|
+
* The hook activity is the most flexible activity type. Depending on its
|
|
15
|
+
* YAML configuration, it operates in one of the following modes:
|
|
16
|
+
*
|
|
17
|
+
* ## Time Hook (Sleep)
|
|
18
|
+
*
|
|
19
|
+
* Pauses the flow for a specified duration in seconds. The `sleep` value
|
|
20
|
+
* can be a literal number or a `@pipe` expression for dynamic delays
|
|
21
|
+
* (e.g., exponential backoff).
|
|
22
|
+
*
|
|
23
|
+
* ```yaml
|
|
24
|
+
* app:
|
|
25
|
+
* id: myapp
|
|
26
|
+
* version: '1'
|
|
27
|
+
* graphs:
|
|
28
|
+
* - subscribes: job.start
|
|
29
|
+
* expire: 300
|
|
30
|
+
*
|
|
31
|
+
* activities:
|
|
32
|
+
* t1:
|
|
33
|
+
* type: trigger
|
|
34
|
+
*
|
|
35
|
+
* delay:
|
|
36
|
+
* type: hook
|
|
37
|
+
* sleep: 60 # pause for 60 seconds
|
|
38
|
+
* job:
|
|
39
|
+
* maps:
|
|
40
|
+
* paused_at: '{$self.output.metadata.ac}'
|
|
41
|
+
*
|
|
42
|
+
* resume:
|
|
43
|
+
* type: hook
|
|
44
|
+
*
|
|
45
|
+
* transitions:
|
|
46
|
+
* t1:
|
|
47
|
+
* - to: delay
|
|
48
|
+
* delay:
|
|
49
|
+
* - to: resume
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* ## Web Hook (External Signal)
|
|
53
|
+
*
|
|
54
|
+
* Registers a webhook listener on a named topic. The flow pauses until
|
|
55
|
+
* an external signal is sent to the hook's topic. The signal data becomes
|
|
56
|
+
* available as `$self.hook.data`. The `hooks` section at the graph level
|
|
57
|
+
* routes incoming signals to the waiting activity.
|
|
58
|
+
*
|
|
59
|
+
* ```yaml
|
|
60
|
+
* app:
|
|
61
|
+
* id: myapp
|
|
62
|
+
* version: '1'
|
|
63
|
+
* graphs:
|
|
64
|
+
* - subscribes: order.placed
|
|
65
|
+
* expire: 3600
|
|
66
|
+
*
|
|
67
|
+
* activities:
|
|
68
|
+
* t1:
|
|
69
|
+
* type: trigger
|
|
70
|
+
*
|
|
71
|
+
* wait_for_approval:
|
|
72
|
+
* type: hook
|
|
73
|
+
* hook:
|
|
74
|
+
* type: object
|
|
75
|
+
* properties:
|
|
76
|
+
* approved: { type: boolean }
|
|
77
|
+
* job:
|
|
78
|
+
* maps:
|
|
79
|
+
* approved: '{$self.hook.data.approved}'
|
|
80
|
+
*
|
|
81
|
+
* done:
|
|
82
|
+
* type: hook
|
|
83
|
+
*
|
|
84
|
+
* transitions:
|
|
85
|
+
* t1:
|
|
86
|
+
* - to: wait_for_approval
|
|
87
|
+
* wait_for_approval:
|
|
88
|
+
* - to: done
|
|
89
|
+
*
|
|
90
|
+
* hooks:
|
|
91
|
+
* order.approval: # external topic that delivers the signal
|
|
92
|
+
* - to: wait_for_approval
|
|
93
|
+
* conditions:
|
|
94
|
+
* match:
|
|
95
|
+
* - expected: '{t1.output.data.id}'
|
|
96
|
+
* actual: '{$self.hook.data.id}'
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* ## Passthrough (No Hook)
|
|
100
|
+
*
|
|
101
|
+
* When neither `sleep` nor `hook` is configured, the hook activity acts
|
|
102
|
+
* as a passthrough: it maps data and immediately transitions to children.
|
|
103
|
+
* This is useful for data transformation, convergence points, or as a
|
|
104
|
+
* cycle pivot (with `cycle: true`).
|
|
105
|
+
*
|
|
106
|
+
* ```yaml
|
|
107
|
+
* app:
|
|
108
|
+
* id: myapp
|
|
109
|
+
* version: '1'
|
|
110
|
+
* graphs:
|
|
111
|
+
* - subscribes: job.start
|
|
112
|
+
*
|
|
113
|
+
* activities:
|
|
114
|
+
* t1:
|
|
115
|
+
* type: trigger
|
|
116
|
+
*
|
|
117
|
+
* pivot:
|
|
118
|
+
* type: hook
|
|
119
|
+
* cycle: true # enables re-entry from a cycle activity
|
|
120
|
+
* output:
|
|
121
|
+
* maps:
|
|
122
|
+
* retryCount: 0
|
|
123
|
+
* job:
|
|
124
|
+
* maps:
|
|
125
|
+
* counter: '{$self.output.data.retryCount}'
|
|
126
|
+
*
|
|
127
|
+
* do_work:
|
|
128
|
+
* type: worker
|
|
129
|
+
* topic: work.do
|
|
130
|
+
*
|
|
131
|
+
* transitions:
|
|
132
|
+
* t1:
|
|
133
|
+
* - to: pivot
|
|
134
|
+
* pivot:
|
|
135
|
+
* - to: do_work
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* ## Execution Model
|
|
139
|
+
*
|
|
140
|
+
* - **With `sleep` or `hook`**: Category A (duplex). Leg 1 registers the
|
|
141
|
+
* hook and saves state. Leg 2 fires when the timer expires or the
|
|
142
|
+
* external signal arrives (via `processTimeHookEvent` or
|
|
143
|
+
* `processWebHookEvent`).
|
|
144
|
+
* - **Without `sleep` or `hook`**: Category B (passthrough). Uses the
|
|
145
|
+
* crash-safe `executeLeg1StepProtocol` to map data and transition
|
|
146
|
+
* to adjacent activities.
|
|
147
|
+
*
|
|
148
|
+
* @see {@link HookActivity} for the TypeScript interface
|
|
11
149
|
*/
|
|
12
150
|
declare class Hook extends Activity {
|
|
13
151
|
config: HookActivity;
|
|
14
152
|
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
15
153
|
process(): Promise<string>;
|
|
154
|
+
/**
|
|
155
|
+
* Static config check: does this activity have a hook or sleep config?
|
|
156
|
+
* Used for routing before context is loaded.
|
|
157
|
+
*/
|
|
158
|
+
isConfiguredAsHook(): boolean;
|
|
16
159
|
/**
|
|
17
160
|
* does this activity use a time-hook or web-hook
|
|
18
161
|
*/
|