@hotmeshio/hotmesh 0.0.7 → 0.0.8
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/build/cjs/package.json +3 -1
- package/build/cjs/services/activities/activity.d.ts +6 -0
- package/build/cjs/services/activities/activity.js +83 -7
- package/build/cjs/services/activities/await.d.ts +2 -2
- package/build/cjs/services/activities/await.js +5 -5
- package/build/cjs/services/activities/cycle.d.ts +19 -0
- package/build/cjs/services/activities/cycle.js +77 -0
- package/build/cjs/services/activities/index.d.ts +4 -2
- package/build/cjs/services/activities/index.js +4 -2
- package/build/cjs/services/activities/worker.d.ts +0 -8
- package/build/cjs/services/activities/worker.js +1 -87
- package/build/cjs/services/collator/index.d.ts +18 -1
- package/build/cjs/services/collator/index.js +41 -11
- package/build/cjs/services/durable/factory.js +17 -1
- package/build/cjs/services/durable/worker.d.ts +1 -0
- package/build/cjs/services/durable/worker.js +9 -14
- package/build/cjs/services/durable/workflow.js +5 -1
- package/build/cjs/services/mapper/index.js +3 -0
- package/build/cjs/services/signaler/stream.js +0 -1
- package/build/cjs/types/activity.d.ts +7 -2
- package/build/cjs/types/index.d.ts +1 -1
- package/build/esm/package.json +3 -1
- package/build/esm/services/activities/activity.d.ts +6 -0
- package/build/esm/services/activities/activity.js +83 -7
- package/build/esm/services/activities/await.d.ts +2 -2
- package/build/esm/services/activities/await.js +5 -5
- package/build/esm/services/activities/cycle.d.ts +19 -0
- package/build/esm/services/activities/cycle.js +74 -0
- package/build/esm/services/activities/index.d.ts +4 -2
- package/build/esm/services/activities/index.js +4 -2
- package/build/esm/services/activities/worker.d.ts +0 -8
- package/build/esm/services/activities/worker.js +1 -87
- package/build/esm/services/collator/index.d.ts +18 -1
- package/build/esm/services/collator/index.js +41 -11
- package/build/esm/services/durable/factory.js +17 -1
- package/build/esm/services/durable/worker.d.ts +1 -0
- package/build/esm/services/durable/worker.js +9 -14
- package/build/esm/services/durable/workflow.js +5 -1
- package/build/esm/services/mapper/index.js +3 -0
- package/build/esm/services/signaler/stream.js +0 -1
- package/build/esm/types/activity.d.ts +7 -2
- package/build/esm/types/index.d.ts +1 -1
- package/package.json +3 -1
- package/services/activities/activity.ts +90 -7
- package/services/activities/await.ts +5 -5
- package/services/activities/cycle.ts +96 -0
- package/services/activities/index.ts +4 -2
- package/services/activities/worker.ts +2 -93
- package/services/collator/index.ts +43 -11
- package/services/durable/factory.ts +17 -1
- package/services/durable/worker.ts +10 -13
- package/services/durable/workflow.ts +4 -1
- package/services/mapper/index.ts +3 -0
- package/services/signaler/stream.ts +0 -1
- package/types/activity.ts +8 -1
- package/types/index.ts +1 -0
|
@@ -43,7 +43,7 @@ class Worker extends Activity {
|
|
|
43
43
|
this.mapInputData();
|
|
44
44
|
|
|
45
45
|
const multi = this.store.getMulti();
|
|
46
|
-
//await this.registerTimeout();
|
|
46
|
+
//todo: await this.registerTimeout();
|
|
47
47
|
await CollatorService.authorizeReentry(this, multi);
|
|
48
48
|
await this.setState(multi);
|
|
49
49
|
await this.setStatus(0, multi);
|
|
@@ -56,13 +56,12 @@ class Worker extends Activity {
|
|
|
56
56
|
'app.activity.mid': messageId,
|
|
57
57
|
'app.job.jss': jobStatus
|
|
58
58
|
});
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
return this.context.metadata.aid;
|
|
61
61
|
} catch (error) {
|
|
62
62
|
if (error instanceof GetStateError) {
|
|
63
63
|
this.logger.error('worker-get-state-error', error);
|
|
64
64
|
} else {
|
|
65
|
-
console.error(error);
|
|
66
65
|
this.logger.error('worker-process-error', error);
|
|
67
66
|
}
|
|
68
67
|
telemetry.setActivityError(error.message);
|
|
@@ -92,96 +91,6 @@ class Worker extends Activity {
|
|
|
92
91
|
}
|
|
93
92
|
return (await this.engine.streamSignaler?.publishMessage(this.config.subtype, streamData)) as string;
|
|
94
93
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//******** SIGNAL RE-ENTRY POINT (DUPLEX LEG 2 of 2) ********//
|
|
98
|
-
async processEvent(status: StreamStatus = StreamStatus.SUCCESS, code: StreamCode = 200): Promise<void> {
|
|
99
|
-
this.setLeg(2);
|
|
100
|
-
const jid = this.context.metadata.jid;
|
|
101
|
-
const aid = this.metadata.aid;
|
|
102
|
-
this.status = status;
|
|
103
|
-
this.code = code;
|
|
104
|
-
this.logger.debug('worker-process-event', { topic: this.config.subtype, jid, aid, status, code });
|
|
105
|
-
let telemetry: TelemetryService;
|
|
106
|
-
try {
|
|
107
|
-
await this.getState();
|
|
108
|
-
const aState = await CollatorService.notarizeReentry(this);
|
|
109
|
-
this.adjacentIndex = CollatorService.getDimensionalIndex(aState);
|
|
110
|
-
|
|
111
|
-
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
112
|
-
let isComplete = CollatorService.isActivityComplete(this.context.metadata.js);
|
|
113
|
-
|
|
114
|
-
if (isComplete) {
|
|
115
|
-
this.logger.warn('worker-process-event-duplicate', { jid, aid });
|
|
116
|
-
this.logger.debug('worker-process-event-duplicate-resolution', { resolution: 'Increase HotMesh config `reclaimDelay` timeout.' });
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
telemetry.startActivitySpan(this.leg);
|
|
120
|
-
if (status === StreamStatus.PENDING) {
|
|
121
|
-
await this.processPending(telemetry);
|
|
122
|
-
} else if (status === StreamStatus.SUCCESS) {
|
|
123
|
-
await this.processSuccess(telemetry);
|
|
124
|
-
} else {
|
|
125
|
-
await this.processError(telemetry);
|
|
126
|
-
}
|
|
127
|
-
} catch (error) {
|
|
128
|
-
this.logger.error('worker-process-event-error', error);
|
|
129
|
-
telemetry.setActivityError(error.message);
|
|
130
|
-
throw error;
|
|
131
|
-
} finally {
|
|
132
|
-
telemetry.endActivitySpan();
|
|
133
|
-
this.logger.debug('worker-process-event-end', { jid, aid });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async processPending(telemetry: TelemetryService): Promise<void> {
|
|
138
|
-
this.bindActivityData('output');
|
|
139
|
-
this.adjacencyList = await this.filterAdjacent();
|
|
140
|
-
this.mapJobData();
|
|
141
|
-
const multi = this.store.getMulti();
|
|
142
|
-
await this.setState(multi);
|
|
143
|
-
await CollatorService.notarizeContinuation(this, multi);
|
|
144
|
-
|
|
145
|
-
await this.setStatus(this.adjacencyList.length, multi);
|
|
146
|
-
const multiResponse = await multi.exec() as MultiResponseFlags;
|
|
147
|
-
this.transitionAdjacent(multiResponse, telemetry);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async processSuccess(telemetry: TelemetryService): Promise<void> {
|
|
151
|
-
this.bindActivityData('output');
|
|
152
|
-
this.adjacencyList = await this.filterAdjacent();
|
|
153
|
-
this.mapJobData();
|
|
154
|
-
const multi = this.store.getMulti();
|
|
155
|
-
await this.setState(multi);
|
|
156
|
-
await CollatorService.notarizeCompletion(this, multi);
|
|
157
|
-
|
|
158
|
-
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
159
|
-
const multiResponse = await multi.exec() as MultiResponseFlags;
|
|
160
|
-
this.transitionAdjacent(multiResponse, telemetry);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async processError(telemetry: TelemetryService): Promise<void> {
|
|
164
|
-
this.bindActivityError(this.data);
|
|
165
|
-
this.adjacencyList = await this.filterAdjacent();
|
|
166
|
-
const multi = this.store.getMulti();
|
|
167
|
-
await this.setState(multi);
|
|
168
|
-
await CollatorService.notarizeCompletion(this, multi);
|
|
169
|
-
|
|
170
|
-
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
171
|
-
const multiResponse = await multi.exec() as MultiResponseFlags;
|
|
172
|
-
this.transitionAdjacent(multiResponse, telemetry);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async transitionAdjacent(multiResponse: MultiResponseFlags, telemetry: TelemetryService): Promise<void> {
|
|
176
|
-
telemetry.mapActivityAttributes();
|
|
177
|
-
const jobStatus = this.resolveStatus(multiResponse);
|
|
178
|
-
const attrs: StringScalarType = { 'app.job.jss': jobStatus };
|
|
179
|
-
const messageIds = await this.transition(this.adjacencyList, jobStatus);
|
|
180
|
-
if (messageIds.length) {
|
|
181
|
-
attrs['app.activity.mids'] = messageIds.join(',')
|
|
182
|
-
}
|
|
183
|
-
telemetry.setActivityAttributes(attrs);
|
|
184
|
-
}
|
|
185
94
|
}
|
|
186
95
|
|
|
187
96
|
export { Worker };
|
|
@@ -4,22 +4,45 @@ import { CollationFaultType, CollationStage } from '../../types/collator';
|
|
|
4
4
|
import { ActivityDuplex } from '../../types/activity';
|
|
5
5
|
import { HotMeshGraph } from '../../types/hotmesh';
|
|
6
6
|
import { Activity } from '../activities/activity';
|
|
7
|
+
import { Cycle } from '../activities/cycle';
|
|
7
8
|
|
|
8
9
|
class CollatorService {
|
|
9
10
|
|
|
10
11
|
//max int digit count that supports `hincrby`
|
|
11
12
|
static targetLength = 15;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* returns the dimensional address (dad) for the target; due
|
|
16
|
+
* to the nature of the notary system, the dad for leg 2 entry
|
|
17
|
+
* must target the `0` index while leg 2 exit must target the
|
|
18
|
+
* current index (0)
|
|
19
|
+
*/
|
|
20
|
+
static getDimensionalAddress(activity: Activity, isEntry = false): Record<string, string> {
|
|
14
21
|
let dad = activity.context.metadata.dad || activity.metadata.dad;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// dad = `${dad.substring(0, dad.lastIndexOf(','))},0`;
|
|
19
|
-
// }
|
|
22
|
+
if (isEntry && dad && activity.leg === 2) {
|
|
23
|
+
dad = `${dad.substring(0, dad.lastIndexOf(','))},0`;
|
|
24
|
+
}
|
|
20
25
|
return CollatorService.getDimensionsById([...activity.config.ancestors, activity.metadata.aid], dad);
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
/**
|
|
29
|
+
* resolves the dimensional address for the
|
|
30
|
+
* ancestor in the graph to go back to. this address
|
|
31
|
+
* is determined by trimming the last digits from
|
|
32
|
+
* the `dad` (including the target).
|
|
33
|
+
* the target activity index is then set to `0`, so that
|
|
34
|
+
* the origin node can be queried for approval/entry.
|
|
35
|
+
*/
|
|
36
|
+
static resolveReentryDimension(activity: Cycle) {
|
|
37
|
+
const targetActivityId = activity.config.ancestor;
|
|
38
|
+
const ancestors = activity.config.ancestors;
|
|
39
|
+
const ancestorIndex = ancestors.indexOf(targetActivityId);
|
|
40
|
+
const dimensions = activity.metadata.dad.split(','); //e.g., `,0,0,1,0`
|
|
41
|
+
dimensions.length = ancestorIndex + 1;
|
|
42
|
+
dimensions.push('0');
|
|
43
|
+
return dimensions.join(',');
|
|
44
|
+
}
|
|
45
|
+
|
|
23
46
|
static async notarizeEntry(activity: Activity, multi?: RedisMulti): Promise<number> {
|
|
24
47
|
//decrement by -100_000_000_000_000
|
|
25
48
|
const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -100_000_000_000_000, this.getDimensionalAddress(activity), multi);
|
|
@@ -35,14 +58,21 @@ class CollatorService {
|
|
|
35
58
|
return amount;
|
|
36
59
|
}
|
|
37
60
|
|
|
61
|
+
static async notarizeEarlyExit(activity: Activity, multi?: RedisMulti): Promise<number> {
|
|
62
|
+
//decrement the 2nd and 3rd digits to fully deactivate (`cycle` activities use this command to fully exit after leg 1) (should result in `888000000000000`)
|
|
63
|
+
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -11_000_000_000_000, this.getDimensionalAddress(activity), multi);
|
|
64
|
+
};
|
|
65
|
+
|
|
38
66
|
static async notarizeEarlyCompletion(activity: Activity, multi?: RedisMulti): Promise<number> {
|
|
39
|
-
//initialize both `possible` (1m) and `actualized` (1) zero dimension, while decrementing the 2nd
|
|
40
|
-
|
|
67
|
+
//initialize both `possible` (1m) and `actualized` (1) zero dimension, while decrementing the 2nd
|
|
68
|
+
//3rd digit is optionally kept open if the activity might be used in a cycle
|
|
69
|
+
const decrement = activity.config.cycle ? 10_000_000_000_000 : 11_000_000_000_000;
|
|
70
|
+
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1_000_001 - decrement, this.getDimensionalAddress(activity), multi);
|
|
41
71
|
};
|
|
42
72
|
|
|
43
73
|
static async notarizeReentry(activity: Activity, multi?: RedisMulti): Promise<number> {
|
|
44
74
|
//increment by 1_000_000 (indicates re-entry and is used to drive the 'dimensional address' for adjacent activities (minus 1))
|
|
45
|
-
const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1_000_000, this.getDimensionalAddress(activity), multi);
|
|
75
|
+
const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1_000_000, this.getDimensionalAddress(activity, true), multi);
|
|
46
76
|
this.verifyInteger(amount, 2, 'enter');
|
|
47
77
|
return amount;
|
|
48
78
|
};
|
|
@@ -53,8 +83,10 @@ class CollatorService {
|
|
|
53
83
|
};
|
|
54
84
|
|
|
55
85
|
static async notarizeCompletion(activity: Activity, multi?: RedisMulti): Promise<number> {
|
|
56
|
-
//
|
|
57
|
-
|
|
86
|
+
//1) ALWAYS actualize leg2 dimension (+1)
|
|
87
|
+
//2) IF the activity is used in a cycle, don't close leg 2!
|
|
88
|
+
const decrement = activity.config.cycle ? 0 : -1_000_000_000_000;
|
|
89
|
+
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1 - decrement, this.getDimensionalAddress(activity), multi);
|
|
58
90
|
};
|
|
59
91
|
|
|
60
92
|
static getDigitAtIndex(num: number, targetDigitIndex: number): number | null {
|
|
@@ -26,7 +26,12 @@ const getWorkflowYAML = (topic: string, version = '1') => {
|
|
|
26
26
|
type: trigger
|
|
27
27
|
stats:
|
|
28
28
|
id: '{$self.input.data.workflowId}'
|
|
29
|
+
|
|
29
30
|
a1:
|
|
31
|
+
type: activity
|
|
32
|
+
cycle: true
|
|
33
|
+
|
|
34
|
+
w1:
|
|
30
35
|
type: worker
|
|
31
36
|
topic: ${topic}
|
|
32
37
|
input:
|
|
@@ -49,9 +54,20 @@ const getWorkflowYAML = (topic: string, version = '1') => {
|
|
|
49
54
|
job:
|
|
50
55
|
maps:
|
|
51
56
|
response: '{$self.output.data.response}'
|
|
57
|
+
|
|
58
|
+
c1:
|
|
59
|
+
type: cycle
|
|
60
|
+
ancestor: a1
|
|
52
61
|
transitions:
|
|
53
62
|
t1:
|
|
54
|
-
- to: a1
|
|
63
|
+
- to: a1
|
|
64
|
+
a1:
|
|
65
|
+
- to: w1
|
|
66
|
+
w1:
|
|
67
|
+
- to: c1
|
|
68
|
+
conditions:
|
|
69
|
+
code: 500
|
|
70
|
+
`;
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
const getActivityYAML = (topic: string, version = '1') => {
|
|
@@ -44,6 +44,7 @@ export class WorkerService {
|
|
|
44
44
|
static connection: Connection;
|
|
45
45
|
static instances = new Map<string, HotMesh | Promise<HotMesh>>();
|
|
46
46
|
workflowRunner: HotMesh;
|
|
47
|
+
activityRunner: HotMesh;
|
|
47
48
|
|
|
48
49
|
static getHotMesh = async (worflowTopic: string) => {
|
|
49
50
|
if (WorkerService.instances.has(worflowTopic)) {
|
|
@@ -114,8 +115,8 @@ export class WorkerService {
|
|
|
114
115
|
|
|
115
116
|
//initialize supporting workflows
|
|
116
117
|
const worker = new WorkerService();
|
|
117
|
-
|
|
118
|
-
await WorkerService.activateWorkflow(activityRunner, activityTopic, getActivityYAML);
|
|
118
|
+
worker.activityRunner = await worker.initActivityWorkflow(config, activityTopic);
|
|
119
|
+
await WorkerService.activateWorkflow(worker.activityRunner, activityTopic, getActivityYAML);
|
|
119
120
|
worker.workflowRunner = await worker.initWorkerWorkflow(config, workflowTopic, workflowFunction);
|
|
120
121
|
await WorkerService.activateWorkflow(worker.workflowRunner, workflowTopic, getWorkflowYAML);
|
|
121
122
|
return worker;
|
|
@@ -134,11 +135,7 @@ export class WorkerService {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
async run() {
|
|
137
|
-
|
|
138
|
-
this.workflowRunner.engine.logger.info('WorkerService is running');
|
|
139
|
-
} else {
|
|
140
|
-
console.log('WorkerService is running');
|
|
141
|
-
}
|
|
138
|
+
this.workflowRunner.engine.logger.info('WorkerService is running');
|
|
142
139
|
}
|
|
143
140
|
|
|
144
141
|
async initActivityWorkflow(config: WorkerConfig, activityTopic: string): Promise<HotMesh> {
|
|
@@ -175,10 +172,11 @@ export class WorkerService {
|
|
|
175
172
|
data: { response: pojoResponse }
|
|
176
173
|
};
|
|
177
174
|
} catch (err) {
|
|
178
|
-
|
|
179
|
-
//todo (make retry configurable)
|
|
175
|
+
this.activityRunner.engine.logger.error('durable-worker-activity-err', err);
|
|
180
176
|
return {
|
|
181
|
-
status: StreamStatus.
|
|
177
|
+
status: StreamStatus.ERROR,
|
|
178
|
+
code: 500,
|
|
179
|
+
message: err.message,
|
|
182
180
|
metadata: { ...data.metadata },
|
|
183
181
|
data: { error: err }
|
|
184
182
|
} as StreamDataResponse;
|
|
@@ -195,7 +193,7 @@ export class WorkerService {
|
|
|
195
193
|
await hotMesh.deploy(getActivityYAML(activityTopic, version));
|
|
196
194
|
await hotMesh.activate(version);
|
|
197
195
|
} catch (err) {
|
|
198
|
-
|
|
196
|
+
hotMesh.engine.logger.error('durable-worker-activity-deploy-activate-error', err);
|
|
199
197
|
throw err;
|
|
200
198
|
}
|
|
201
199
|
} else if(app && !app.active) {
|
|
@@ -260,10 +258,9 @@ export class WorkerService {
|
|
|
260
258
|
data: { response: workflowResponse }
|
|
261
259
|
};
|
|
262
260
|
} catch (err) {
|
|
263
|
-
//todo: (retryable error types)
|
|
264
261
|
return {
|
|
262
|
+
status: StreamStatus.ERROR,
|
|
265
263
|
code: 500,
|
|
266
|
-
status: StreamStatus.PENDING,
|
|
267
264
|
metadata: { ...data.metadata },
|
|
268
265
|
data: { error: err }
|
|
269
266
|
} as StreamDataResponse;
|
|
@@ -103,7 +103,10 @@ export class WorkflowService {
|
|
|
103
103
|
try {
|
|
104
104
|
const hmshInstance = await WorkerService.getHotMesh(activityTopic);
|
|
105
105
|
activityState = await hmshInstance.getState(activityTopic, activityJobId);
|
|
106
|
-
if (activityState.metadata.
|
|
106
|
+
if (activityState.metadata.err) {
|
|
107
|
+
await hmshInstance.scrub(activityJobId);
|
|
108
|
+
throw new Error(activityState.metadata.err);
|
|
109
|
+
} else if (activityState.metadata.js === 0) {
|
|
107
110
|
//return immediately
|
|
108
111
|
return activityState.data?.response as T;
|
|
109
112
|
}
|
package/services/mapper/index.ts
CHANGED
|
@@ -60,6 +60,9 @@ class MapperService {
|
|
|
60
60
|
return transitionRule;
|
|
61
61
|
}
|
|
62
62
|
if (code.toString() === (transitionRule.code || 200).toString()) {
|
|
63
|
+
if (!transitionRule.match) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
63
66
|
const orGate = transitionRule.gate === 'or';
|
|
64
67
|
let allAreTrue = true;
|
|
65
68
|
let someAreTrue = false;
|
package/types/activity.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MetricTypes } from "./stats";
|
|
2
2
|
import { StreamRetryPolicy } from "./stream";
|
|
3
3
|
|
|
4
|
-
type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'iterate';
|
|
4
|
+
type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'iterate' | 'cycle';
|
|
5
5
|
|
|
6
6
|
type Consumes = Record<string, string[]>;
|
|
7
7
|
|
|
@@ -18,6 +18,7 @@ interface BaseActivity {
|
|
|
18
18
|
sleep?: number; //@pipe /in seconds
|
|
19
19
|
expire?: number; //-1 forever (15 seconds default); todo: make globally configurable
|
|
20
20
|
retry?: StreamRetryPolicy
|
|
21
|
+
cycle?: boolean; //if true, the `notary` will leave leg 2 open, so it can be re/cycled
|
|
21
22
|
collationInt?: number; //compiler
|
|
22
23
|
consumes?: Consumes; //compiler
|
|
23
24
|
PRODUCES?: string[]; //compiler
|
|
@@ -61,6 +62,11 @@ interface EmitActivity extends BaseActivity {
|
|
|
61
62
|
type: 'emit';
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
interface CycleActivity extends BaseActivity {
|
|
66
|
+
type: 'cycle';
|
|
67
|
+
ancestor: string; //ancestor activity id
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
interface IterateActivity extends BaseActivity {
|
|
65
71
|
type: 'iterate';
|
|
66
72
|
}
|
|
@@ -108,6 +114,7 @@ export {
|
|
|
108
114
|
Consumes,
|
|
109
115
|
TriggerActivityStats,
|
|
110
116
|
AwaitActivity,
|
|
117
|
+
CycleActivity,
|
|
111
118
|
BaseActivity,
|
|
112
119
|
EmitActivity,
|
|
113
120
|
IterateActivity,
|