@hotmeshio/hotmesh 0.0.16 → 0.0.18
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/modules/utils.d.ts +3 -0
- package/build/modules/utils.js +10 -1
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +4 -12
- package/build/services/activities/activity.js +19 -156
- package/build/services/activities/hook.d.ts +20 -0
- package/build/services/activities/hook.js +124 -0
- package/build/services/activities/index.d.ts +2 -0
- package/build/services/activities/index.js +2 -0
- package/build/services/activities/trigger.js +1 -1
- package/build/services/collator/index.js +0 -1
- package/build/services/compiler/deployer.d.ts +2 -0
- package/build/services/compiler/deployer.js +29 -2
- package/build/services/durable/client.d.ts +8 -1
- package/build/services/durable/client.js +47 -0
- package/build/services/durable/factory.js +88 -11
- package/build/services/durable/search.d.ts +15 -0
- package/build/services/durable/search.js +45 -0
- package/build/services/durable/workflow.d.ts +1 -0
- package/build/services/durable/workflow.js +36 -0
- package/build/services/engine/index.d.ts +7 -2
- package/build/services/engine/index.js +2 -1
- package/build/services/store/clients/ioredis.d.ts +1 -0
- package/build/services/store/clients/ioredis.js +12 -0
- package/build/services/store/clients/redis.d.ts +1 -0
- package/build/services/store/clients/redis.js +3 -0
- package/build/services/store/index.d.ts +1 -0
- package/build/services/telemetry/index.js +2 -1
- package/build/types/activity.d.ts +6 -3
- package/build/types/durable.d.ts +11 -1
- package/build/types/hook.d.ts +1 -0
- package/build/types/index.d.ts +2 -2
- package/modules/utils.ts +11 -0
- package/package.json +1 -1
- package/services/activities/activity.ts +20 -167
- package/services/activities/hook.ts +149 -0
- package/services/activities/index.ts +2 -0
- package/services/activities/trigger.ts +1 -1
- package/services/collator/index.ts +0 -1
- package/services/compiler/deployer.ts +32 -2
- package/services/durable/client.ts +51 -2
- package/services/durable/factory.ts +88 -11
- package/services/durable/search.ts +54 -0
- package/services/durable/workflow.ts +34 -1
- package/services/engine/index.ts +8 -4
- package/services/store/clients/ioredis.ts +13 -0
- package/services/store/clients/redis.ts +4 -0
- package/services/store/index.ts +1 -0
- package/services/telemetry/index.ts +2 -1
- package/types/activity.ts +7 -2
- package/types/durable.ts +9 -0
- package/types/hook.ts +1 -0
- package/types/index.ts +2 -0
package/build/modules/utils.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ import { StringAnyType } from "../types/serializer";
|
|
|
6
6
|
import { StreamCode, StreamStatus } from "../types/stream";
|
|
7
7
|
export declare function sleepFor(ms: number): Promise<unknown>;
|
|
8
8
|
export declare function identifyRedisType(redisInstance: any): 'redis' | 'ioredis' | null;
|
|
9
|
+
export declare const polyfill: {
|
|
10
|
+
resolveActivityType(activityType: string): string;
|
|
11
|
+
};
|
|
9
12
|
export declare function identifyRedisTypeFromClass(redisClass: any): 'redis' | 'ioredis' | null;
|
|
10
13
|
export declare function matchesStatusCode(code: StreamCode, pattern: string | RegExp): boolean;
|
|
11
14
|
export declare function matchesStatus(status: StreamStatus, targetStatus: StreamStatus): boolean;
|
package/build/modules/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.identifyRedisType = exports.sleepFor = void 0;
|
|
3
|
+
exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.sleepFor = void 0;
|
|
4
4
|
async function sleepFor(ms) {
|
|
5
5
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
6
|
}
|
|
@@ -21,6 +21,15 @@ function identifyRedisType(redisInstance) {
|
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
23
|
exports.identifyRedisType = identifyRedisType;
|
|
24
|
+
//todo: the polyfill methods will all be deleted in the `beta` release.
|
|
25
|
+
exports.polyfill = {
|
|
26
|
+
resolveActivityType(activityType) {
|
|
27
|
+
if (activityType === 'activity') {
|
|
28
|
+
return 'hook';
|
|
29
|
+
}
|
|
30
|
+
return activityType;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
24
33
|
function identifyRedisTypeFromClass(redisClass) {
|
|
25
34
|
if (redisClass && redisClass.name === 'Redis' || redisClass.name === 'EventEmitter') {
|
|
26
35
|
return 'ioredis';
|
package/build/package.json
CHANGED
|
@@ -7,7 +7,6 @@ import { JobState, JobStatus } from '../../types/job';
|
|
|
7
7
|
import { MultiResponseFlags, RedisClient, RedisMulti } from '../../types/redis';
|
|
8
8
|
import { StringAnyType } from '../../types/serializer';
|
|
9
9
|
import { StreamCode, StreamData, StreamStatus } from '../../types/stream';
|
|
10
|
-
import { HookRule } from '../../types/hook';
|
|
11
10
|
/**
|
|
12
11
|
* The base class for all activities
|
|
13
12
|
*/
|
|
@@ -26,18 +25,11 @@ declare class Activity {
|
|
|
26
25
|
adjacencyList: StreamData[];
|
|
27
26
|
adjacentIndex: number;
|
|
28
27
|
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
29
|
-
process(): Promise<string>;
|
|
30
28
|
setLeg(leg: ActivityLeg): void;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
processTimeHookEvent(jobId: string): Promise<JobStatus | void>;
|
|
36
|
-
processHookEvent(jobId: string): Promise<JobStatus | void>;
|
|
37
|
-
processEvent(status?: StreamStatus, code?: StreamCode): Promise<void>;
|
|
38
|
-
processPending(telemetry: TelemetryService): Promise<MultiResponseFlags>;
|
|
39
|
-
processSuccess(telemetry: TelemetryService): Promise<MultiResponseFlags>;
|
|
40
|
-
processError(telemetry: TelemetryService): Promise<MultiResponseFlags>;
|
|
29
|
+
processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output', jobId?: string): Promise<void>;
|
|
30
|
+
processPending(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
|
|
31
|
+
processSuccess(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
|
|
32
|
+
processError(telemetry: TelemetryService, type: string): Promise<MultiResponseFlags>;
|
|
41
33
|
transitionAdjacent(multiResponse: MultiResponseFlags, telemetry: TelemetryService): Promise<void>;
|
|
42
34
|
resolveStatus(multiResponse: MultiResponseFlags): number;
|
|
43
35
|
mapJobData(): void;
|
|
@@ -7,7 +7,6 @@ const collator_1 = require("../collator");
|
|
|
7
7
|
const mapper_1 = require("../mapper");
|
|
8
8
|
const pipe_1 = require("../pipe");
|
|
9
9
|
const serializer_1 = require("../serializer");
|
|
10
|
-
const store_1 = require("../signaler/store");
|
|
11
10
|
const telemetry_1 = require("../telemetry");
|
|
12
11
|
const stream_1 = require("../../types/stream");
|
|
13
12
|
/**
|
|
@@ -17,7 +16,7 @@ class Activity {
|
|
|
17
16
|
constructor(config, data, metadata, hook, engine, context) {
|
|
18
17
|
this.status = stream_1.StreamStatus.SUCCESS;
|
|
19
18
|
this.code = 200;
|
|
20
|
-
this.adjacentIndex = 0;
|
|
19
|
+
this.adjacentIndex = 0;
|
|
21
20
|
this.config = config;
|
|
22
21
|
this.data = data;
|
|
23
22
|
this.metadata = metadata;
|
|
@@ -27,161 +26,20 @@ class Activity {
|
|
|
27
26
|
this.logger = engine.logger;
|
|
28
27
|
this.store = engine.store;
|
|
29
28
|
}
|
|
30
|
-
//******** INITIAL ENTRY POINT (A) ********//
|
|
31
|
-
async process() {
|
|
32
|
-
this.logger.debug('activity-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
33
|
-
let telemetry;
|
|
34
|
-
try {
|
|
35
|
-
this.setLeg(1);
|
|
36
|
-
await collator_1.CollatorService.notarizeEntry(this);
|
|
37
|
-
await this.getState();
|
|
38
|
-
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
39
|
-
telemetry.startActivitySpan(this.leg);
|
|
40
|
-
let multiResponse;
|
|
41
|
-
const multi = this.store.getMulti();
|
|
42
|
-
if (this.doesHook()) {
|
|
43
|
-
//sleep and wait to awaken upon a signal
|
|
44
|
-
await this.registerHook(multi);
|
|
45
|
-
this.mapOutputData();
|
|
46
|
-
this.mapJobData();
|
|
47
|
-
await this.setState(multi);
|
|
48
|
-
await collator_1.CollatorService.authorizeReentry(this, multi);
|
|
49
|
-
await this.setStatus(0, multi);
|
|
50
|
-
await multi.exec();
|
|
51
|
-
telemetry.mapActivityAttributes();
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
//end the activity and transition to its children
|
|
55
|
-
this.adjacencyList = await this.filterAdjacent();
|
|
56
|
-
this.mapOutputData();
|
|
57
|
-
this.mapJobData();
|
|
58
|
-
await this.setState(multi);
|
|
59
|
-
await collator_1.CollatorService.notarizeEarlyCompletion(this, multi);
|
|
60
|
-
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
61
|
-
multiResponse = await multi.exec();
|
|
62
|
-
telemetry.mapActivityAttributes();
|
|
63
|
-
const jobStatus = this.resolveStatus(multiResponse);
|
|
64
|
-
const attrs = { 'app.job.jss': jobStatus };
|
|
65
|
-
const messageIds = await this.transition(this.adjacencyList, jobStatus);
|
|
66
|
-
if (messageIds.length) {
|
|
67
|
-
attrs['app.activity.mids'] = messageIds.join(',');
|
|
68
|
-
}
|
|
69
|
-
telemetry.setActivityAttributes(attrs);
|
|
70
|
-
}
|
|
71
|
-
return this.context.metadata.aid;
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
if (error instanceof errors_1.GetStateError) {
|
|
75
|
-
this.logger.error('activity-get-state-error', { error });
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
this.logger.error('activity-process-error', { error });
|
|
79
|
-
}
|
|
80
|
-
telemetry.setActivityError(error.message);
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
83
|
-
finally {
|
|
84
|
-
telemetry.endActivitySpan();
|
|
85
|
-
this.logger.debug('activity-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
29
|
setLeg(leg) {
|
|
89
30
|
this.leg = leg;
|
|
90
31
|
}
|
|
91
|
-
//******** SIGNAL RE-ENTRY POINT ********//
|
|
92
|
-
doesHook() {
|
|
93
|
-
return !!(this.config.hook?.topic || this.config.sleep);
|
|
94
|
-
}
|
|
95
|
-
async getHookRule(topic) {
|
|
96
|
-
const rules = await this.store.getHookRules();
|
|
97
|
-
return rules?.[topic]?.[0];
|
|
98
|
-
}
|
|
99
|
-
async registerHook(multi) {
|
|
100
|
-
if (this.config.hook?.topic) {
|
|
101
|
-
const signaler = new store_1.StoreSignaler(this.store, this.logger);
|
|
102
|
-
return await signaler.registerWebHook(this.config.hook.topic, this.context, multi);
|
|
103
|
-
}
|
|
104
|
-
else if (this.config.sleep) {
|
|
105
|
-
const durationInSeconds = pipe_1.Pipe.resolve(this.config.sleep, this.context);
|
|
106
|
-
const jobId = this.context.metadata.jid;
|
|
107
|
-
const activityId = this.metadata.aid;
|
|
108
|
-
const dId = this.metadata.dad;
|
|
109
|
-
await this.engine.task.registerTimeHook(jobId, `${activityId}${dId || ''}`, 'sleep', durationInSeconds);
|
|
110
|
-
return jobId;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
async processWebHookEvent() {
|
|
114
|
-
this.logger.debug('engine-process-web-hook-event', {
|
|
115
|
-
topic: this.config.hook.topic,
|
|
116
|
-
aid: this.metadata.aid
|
|
117
|
-
});
|
|
118
|
-
const signaler = new store_1.StoreSignaler(this.store, this.logger);
|
|
119
|
-
const data = { ...this.data };
|
|
120
|
-
const jobId = await signaler.processWebHookSignal(this.config.hook.topic, data);
|
|
121
|
-
if (jobId) {
|
|
122
|
-
await this.processHookEvent(jobId);
|
|
123
|
-
await signaler.deleteWebHookSignal(this.config.hook.topic, data);
|
|
124
|
-
} //else => already resolved
|
|
125
|
-
}
|
|
126
|
-
async processTimeHookEvent(jobId) {
|
|
127
|
-
this.logger.debug('engine-process-time-hook-event', {
|
|
128
|
-
jid: jobId,
|
|
129
|
-
aid: this.metadata.aid
|
|
130
|
-
});
|
|
131
|
-
return await this.processHookEvent(jobId);
|
|
132
|
-
}
|
|
133
|
-
async processHookEvent(jobId) {
|
|
134
|
-
this.logger.debug('activity-process-hook-event', { jobId });
|
|
135
|
-
let telemetry;
|
|
136
|
-
try {
|
|
137
|
-
this.setLeg(2);
|
|
138
|
-
await this.getState(jobId);
|
|
139
|
-
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
140
|
-
telemetry.startActivitySpan(this.leg);
|
|
141
|
-
const aState = await collator_1.CollatorService.notarizeReentry(this);
|
|
142
|
-
this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(aState);
|
|
143
|
-
this.bindActivityData('hook');
|
|
144
|
-
this.mapJobData();
|
|
145
|
-
this.adjacencyList = await this.filterAdjacent();
|
|
146
|
-
const multi = this.engine.store.getMulti();
|
|
147
|
-
await this.setState(multi);
|
|
148
|
-
await collator_1.CollatorService.notarizeCompletion(this, multi);
|
|
149
|
-
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
150
|
-
const multiResponse = await multi.exec();
|
|
151
|
-
telemetry.mapActivityAttributes();
|
|
152
|
-
const jobStatus = this.resolveStatus(multiResponse);
|
|
153
|
-
const attrs = { 'app.job.jss': jobStatus };
|
|
154
|
-
const messageIds = await this.transition(this.adjacencyList, jobStatus);
|
|
155
|
-
if (messageIds.length) {
|
|
156
|
-
attrs['app.activity.mids'] = messageIds.join(',');
|
|
157
|
-
}
|
|
158
|
-
telemetry.setActivityAttributes(attrs);
|
|
159
|
-
return jobStatus;
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
if (error instanceof errors_1.CollationError && error.fault === 'inactive') {
|
|
163
|
-
this.logger.info('process-hook-event-inactive-error', { error });
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
this.logger.error('engine-process-hook-event-error', { error });
|
|
167
|
-
telemetry.setActivityError(error.message);
|
|
168
|
-
throw error;
|
|
169
|
-
}
|
|
170
|
-
finally {
|
|
171
|
-
telemetry.endActivitySpan();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
32
|
//******** DUPLEX RE-ENTRY POINT ********//
|
|
175
|
-
async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200) {
|
|
33
|
+
async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output', jobId) {
|
|
176
34
|
this.setLeg(2);
|
|
177
|
-
const jid = this.context.metadata.jid;
|
|
35
|
+
const jid = this.context.metadata.jid || jobId;
|
|
178
36
|
const aid = this.metadata.aid;
|
|
179
37
|
this.status = status;
|
|
180
38
|
this.code = code;
|
|
181
39
|
this.logger.debug('activity-process-event', { topic: this.config.subtype, jid, aid, status, code });
|
|
182
40
|
let telemetry;
|
|
183
41
|
try {
|
|
184
|
-
await this.getState();
|
|
42
|
+
await this.getState(jobId);
|
|
185
43
|
const aState = await collator_1.CollatorService.notarizeReentry(this);
|
|
186
44
|
this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(aState);
|
|
187
45
|
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
@@ -194,21 +52,22 @@ class Activity {
|
|
|
194
52
|
telemetry.startActivitySpan(this.leg);
|
|
195
53
|
let multiResponse;
|
|
196
54
|
if (status === stream_1.StreamStatus.PENDING) {
|
|
197
|
-
multiResponse = await this.processPending(telemetry);
|
|
55
|
+
multiResponse = await this.processPending(telemetry, type);
|
|
198
56
|
}
|
|
199
57
|
else if (status === stream_1.StreamStatus.SUCCESS) {
|
|
200
|
-
multiResponse = await this.processSuccess(telemetry);
|
|
58
|
+
multiResponse = await this.processSuccess(telemetry, type);
|
|
201
59
|
}
|
|
202
60
|
else {
|
|
203
|
-
multiResponse = await this.processError(telemetry);
|
|
61
|
+
multiResponse = await this.processError(telemetry, type);
|
|
204
62
|
}
|
|
205
63
|
this.transitionAdjacent(multiResponse, telemetry);
|
|
206
64
|
}
|
|
207
65
|
catch (error) {
|
|
208
|
-
if (error instanceof errors_1.CollationError
|
|
66
|
+
if (error instanceof errors_1.CollationError) {
|
|
209
67
|
this.logger.info('process-event-inactive-error', { error });
|
|
210
68
|
return;
|
|
211
69
|
}
|
|
70
|
+
console.error(error);
|
|
212
71
|
this.logger.error('activity-process-event-error', { error });
|
|
213
72
|
telemetry && telemetry.setActivityError(error.message);
|
|
214
73
|
throw error;
|
|
@@ -218,8 +77,8 @@ class Activity {
|
|
|
218
77
|
this.logger.debug('activity-process-event-end', { jid, aid });
|
|
219
78
|
}
|
|
220
79
|
}
|
|
221
|
-
async processPending(telemetry) {
|
|
222
|
-
this.bindActivityData(
|
|
80
|
+
async processPending(telemetry, type) {
|
|
81
|
+
this.bindActivityData(type);
|
|
223
82
|
this.adjacencyList = await this.filterAdjacent();
|
|
224
83
|
this.mapJobData();
|
|
225
84
|
const multi = this.store.getMulti();
|
|
@@ -228,8 +87,8 @@ class Activity {
|
|
|
228
87
|
await this.setStatus(this.adjacencyList.length, multi);
|
|
229
88
|
return await multi.exec();
|
|
230
89
|
}
|
|
231
|
-
async processSuccess(telemetry) {
|
|
232
|
-
this.bindActivityData(
|
|
90
|
+
async processSuccess(telemetry, type) {
|
|
91
|
+
this.bindActivityData(type);
|
|
233
92
|
this.adjacencyList = await this.filterAdjacent();
|
|
234
93
|
this.mapJobData();
|
|
235
94
|
const multi = this.store.getMulti();
|
|
@@ -238,7 +97,7 @@ class Activity {
|
|
|
238
97
|
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
239
98
|
return await multi.exec();
|
|
240
99
|
}
|
|
241
|
-
async processError(telemetry) {
|
|
100
|
+
async processError(telemetry, type) {
|
|
242
101
|
this.bindActivityError(this.data);
|
|
243
102
|
this.adjacencyList = await this.filterAdjacent();
|
|
244
103
|
const multi = this.store.getMulti();
|
|
@@ -496,7 +355,11 @@ class Activity {
|
|
|
496
355
|
}
|
|
497
356
|
async transition(adjacencyList, jobStatus) {
|
|
498
357
|
let mIds = [];
|
|
499
|
-
|
|
358
|
+
let emit = false;
|
|
359
|
+
if (this.config.emit) {
|
|
360
|
+
emit = pipe_1.Pipe.resolve(this.config.emit, this.context);
|
|
361
|
+
}
|
|
362
|
+
if (jobStatus <= 0 || emit) {
|
|
500
363
|
//activity should not send 'emit' if the job is truly over
|
|
501
364
|
const isTrueEmit = jobStatus > 0;
|
|
502
365
|
await this.engine.runJobCompletionTasks(this.context, isTrueEmit);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EngineService } from '../engine';
|
|
2
|
+
import { ActivityData, ActivityMetadata, ActivityType, HookActivity } from '../../types/activity';
|
|
3
|
+
import { JobState, JobStatus } from '../../types/job';
|
|
4
|
+
import { RedisMulti } from '../../types/redis';
|
|
5
|
+
import { HookRule } from '../../types/hook';
|
|
6
|
+
import { Activity } from './activity';
|
|
7
|
+
/**
|
|
8
|
+
* Listens for `webhook`, `timehook`, and `cycle` (repeat) signals
|
|
9
|
+
*/
|
|
10
|
+
declare class Hook extends Activity {
|
|
11
|
+
config: HookActivity;
|
|
12
|
+
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
13
|
+
process(): Promise<string>;
|
|
14
|
+
doesHook(): boolean;
|
|
15
|
+
getHookRule(topic: string): Promise<HookRule | undefined>;
|
|
16
|
+
registerHook(multi?: RedisMulti): Promise<string | void>;
|
|
17
|
+
processWebHookEvent(): Promise<JobStatus | void>;
|
|
18
|
+
processTimeHookEvent(jobId: string): Promise<JobStatus | void>;
|
|
19
|
+
}
|
|
20
|
+
export { Hook };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Hook = void 0;
|
|
4
|
+
const errors_1 = require("../../modules/errors");
|
|
5
|
+
const collator_1 = require("../collator");
|
|
6
|
+
const pipe_1 = require("../pipe");
|
|
7
|
+
const store_1 = require("../signaler/store");
|
|
8
|
+
const telemetry_1 = require("../telemetry");
|
|
9
|
+
const activity_1 = require("./activity");
|
|
10
|
+
const types_1 = require("../../types");
|
|
11
|
+
/**
|
|
12
|
+
* Listens for `webhook`, `timehook`, and `cycle` (repeat) signals
|
|
13
|
+
*/
|
|
14
|
+
class Hook extends activity_1.Activity {
|
|
15
|
+
constructor(config, data, metadata, hook, engine, context) {
|
|
16
|
+
super(config, data, metadata, hook, engine, context);
|
|
17
|
+
}
|
|
18
|
+
//******** INITIAL ENTRY POINT (A) ********//
|
|
19
|
+
async process() {
|
|
20
|
+
this.logger.debug('hook-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
21
|
+
let telemetry;
|
|
22
|
+
try {
|
|
23
|
+
this.setLeg(1);
|
|
24
|
+
await collator_1.CollatorService.notarizeEntry(this);
|
|
25
|
+
await this.getState();
|
|
26
|
+
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
27
|
+
telemetry.startActivitySpan(this.leg);
|
|
28
|
+
let multiResponse;
|
|
29
|
+
const multi = this.store.getMulti();
|
|
30
|
+
if (this.doesHook()) {
|
|
31
|
+
//sleep and wait to awaken upon a signal
|
|
32
|
+
await this.registerHook(multi);
|
|
33
|
+
this.mapOutputData();
|
|
34
|
+
this.mapJobData();
|
|
35
|
+
await this.setState(multi);
|
|
36
|
+
await collator_1.CollatorService.authorizeReentry(this, multi);
|
|
37
|
+
await this.setStatus(0, multi);
|
|
38
|
+
await multi.exec();
|
|
39
|
+
telemetry.mapActivityAttributes();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
//end the activity and transition to its children
|
|
43
|
+
this.adjacencyList = await this.filterAdjacent();
|
|
44
|
+
this.mapOutputData();
|
|
45
|
+
this.mapJobData();
|
|
46
|
+
await this.setState(multi);
|
|
47
|
+
await collator_1.CollatorService.notarizeEarlyCompletion(this, multi);
|
|
48
|
+
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
49
|
+
multiResponse = await multi.exec();
|
|
50
|
+
telemetry.mapActivityAttributes();
|
|
51
|
+
const jobStatus = this.resolveStatus(multiResponse);
|
|
52
|
+
const attrs = { 'app.job.jss': jobStatus };
|
|
53
|
+
const messageIds = await this.transition(this.adjacencyList, jobStatus);
|
|
54
|
+
if (messageIds.length) {
|
|
55
|
+
attrs['app.activity.mids'] = messageIds.join(',');
|
|
56
|
+
}
|
|
57
|
+
telemetry.setActivityAttributes(attrs);
|
|
58
|
+
}
|
|
59
|
+
return this.context.metadata.aid;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof errors_1.GetStateError) {
|
|
63
|
+
this.logger.error('hook-get-state-error', { error });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.logger.error('hook-process-error', { error });
|
|
67
|
+
}
|
|
68
|
+
telemetry.setActivityError(error.message);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
telemetry.endActivitySpan();
|
|
73
|
+
this.logger.debug('hook-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//******** SIGNAL RE-ENTRY POINT ********//
|
|
77
|
+
doesHook() {
|
|
78
|
+
//does this activity use a time-hook or web-hook
|
|
79
|
+
return !!(this.config.hook?.topic || this.config.sleep);
|
|
80
|
+
}
|
|
81
|
+
async getHookRule(topic) {
|
|
82
|
+
const rules = await this.store.getHookRules();
|
|
83
|
+
return rules?.[topic]?.[0];
|
|
84
|
+
}
|
|
85
|
+
async registerHook(multi) {
|
|
86
|
+
if (this.config.hook?.topic) {
|
|
87
|
+
const signaler = new store_1.StoreSignaler(this.store, this.logger);
|
|
88
|
+
return await signaler.registerWebHook(this.config.hook.topic, this.context, multi);
|
|
89
|
+
}
|
|
90
|
+
else if (this.config.sleep) {
|
|
91
|
+
const durationInSeconds = pipe_1.Pipe.resolve(this.config.sleep, this.context);
|
|
92
|
+
const jobId = this.context.metadata.jid;
|
|
93
|
+
const activityId = this.metadata.aid;
|
|
94
|
+
const dId = this.metadata.dad;
|
|
95
|
+
await this.engine.task.registerTimeHook(jobId, `${activityId}${dId || ''}`, 'sleep', durationInSeconds);
|
|
96
|
+
return jobId;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async processWebHookEvent() {
|
|
100
|
+
this.logger.debug('hook-process-web-hook-event', {
|
|
101
|
+
topic: this.config.hook.topic,
|
|
102
|
+
aid: this.metadata.aid
|
|
103
|
+
});
|
|
104
|
+
const signaler = new store_1.StoreSignaler(this.store, this.logger);
|
|
105
|
+
const data = { ...this.data };
|
|
106
|
+
const jobId = await signaler.processWebHookSignal(this.config.hook.topic, data);
|
|
107
|
+
if (jobId) {
|
|
108
|
+
//if a webhook signal is sent that includes 'keep_alive' the hook will remain open
|
|
109
|
+
const code = data.keep_alive ? 202 : 200;
|
|
110
|
+
await this.processEvent(types_1.StreamStatus.SUCCESS, code, 'hook', jobId);
|
|
111
|
+
if (code === 200) {
|
|
112
|
+
await signaler.deleteWebHookSignal(this.config.hook.topic, data);
|
|
113
|
+
}
|
|
114
|
+
} //else => already resolved
|
|
115
|
+
}
|
|
116
|
+
async processTimeHookEvent(jobId) {
|
|
117
|
+
this.logger.debug('hook-process-time-hook-event', {
|
|
118
|
+
jid: jobId,
|
|
119
|
+
aid: this.metadata.aid
|
|
120
|
+
});
|
|
121
|
+
await this.processEvent(types_1.StreamStatus.SUCCESS, 200, 'hook');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.Hook = Hook;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Activity } from './activity';
|
|
2
2
|
import { Await } from './await';
|
|
3
3
|
import { Cycle } from './cycle';
|
|
4
|
+
import { Hook } from './hook';
|
|
4
5
|
import { Iterate } from './iterate';
|
|
5
6
|
import { Signal } from './signal';
|
|
6
7
|
import { Trigger } from './trigger';
|
|
@@ -9,6 +10,7 @@ declare const _default: {
|
|
|
9
10
|
activity: typeof Activity;
|
|
10
11
|
await: typeof Await;
|
|
11
12
|
cycle: typeof Cycle;
|
|
13
|
+
hook: typeof Hook;
|
|
12
14
|
iterate: typeof Iterate;
|
|
13
15
|
signal: typeof Signal;
|
|
14
16
|
trigger: typeof Trigger;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const activity_1 = require("./activity");
|
|
4
4
|
const await_1 = require("./await");
|
|
5
5
|
const cycle_1 = require("./cycle");
|
|
6
|
+
const hook_1 = require("./hook");
|
|
6
7
|
const iterate_1 = require("./iterate");
|
|
7
8
|
const signal_1 = require("./signal");
|
|
8
9
|
const trigger_1 = require("./trigger");
|
|
@@ -11,6 +12,7 @@ exports.default = {
|
|
|
11
12
|
activity: activity_1.Activity,
|
|
12
13
|
await: await_1.Await,
|
|
13
14
|
cycle: cycle_1.Cycle,
|
|
15
|
+
hook: hook_1.Hook,
|
|
14
16
|
iterate: iterate_1.Iterate,
|
|
15
17
|
signal: signal_1.Signal,
|
|
16
18
|
trigger: trigger_1.Trigger,
|
|
@@ -151,7 +151,7 @@ class Trigger extends activity_1.Activity {
|
|
|
151
151
|
}
|
|
152
152
|
async setStats(multi) {
|
|
153
153
|
const md = this.context.metadata;
|
|
154
|
-
if (this.config.stats?.measures) {
|
|
154
|
+
if (md.key && this.config.stats?.measures) {
|
|
155
155
|
const config = await this.engine.getVID();
|
|
156
156
|
const reporter = new reporter_1.ReporterService(config, this.store, this.logger);
|
|
157
157
|
await this.store.setStats(md.key, md.jid, md.ts, reporter.resolveTriggerStatistics(this.config, this.context), config, multi);
|
|
@@ -45,7 +45,6 @@ class CollatorService {
|
|
|
45
45
|
//set second digit to 8, allowing for re-entry
|
|
46
46
|
//decrement by -10_000_000_000_000
|
|
47
47
|
const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -10000000000000, this.getDimensionalAddress(activity), multi);
|
|
48
|
-
//this.verifyInteger(amount, 1, 'exit');
|
|
49
48
|
return amount;
|
|
50
49
|
}
|
|
51
50
|
static async notarizeEarlyExit(activity, multi) {
|
|
@@ -16,7 +16,9 @@ declare class Deployer {
|
|
|
16
16
|
bindSymbols(startIndex: number, maxIndex: number, existingSymbols: Symbols, prefix: string, produces: string[]): Symbols;
|
|
17
17
|
copyJobSchemas(): void;
|
|
18
18
|
bindBackRefs(): void;
|
|
19
|
+
bindCycleTarget(): void;
|
|
19
20
|
convertTopicsToTypes(): void;
|
|
21
|
+
convertActivitiesToHooks(): void;
|
|
20
22
|
bindParents(): Promise<void>;
|
|
21
23
|
collectValues(schema: Record<string, any>, values: Set<string>): void;
|
|
22
24
|
traverse(obj: any, values: Set<string>): void;
|
|
@@ -17,10 +17,12 @@ class Deployer {
|
|
|
17
17
|
async deploy(store) {
|
|
18
18
|
this.store = store;
|
|
19
19
|
collator_1.CollatorService.compile(this.manifest.app.graphs);
|
|
20
|
+
this.convertActivitiesToHooks();
|
|
20
21
|
this.convertTopicsToTypes();
|
|
21
22
|
this.copyJobSchemas();
|
|
22
23
|
this.bindBackRefs();
|
|
23
24
|
this.bindParents();
|
|
25
|
+
this.bindCycleTarget();
|
|
24
26
|
this.resolveMappingDependencies();
|
|
25
27
|
this.resolveJobMapsPaths();
|
|
26
28
|
await this.generateSymKeys();
|
|
@@ -136,8 +138,21 @@ class Deployer {
|
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
+
//the cycle/goto activity includes and ancestor target;
|
|
142
|
+
//update with the cycle flag, so it can be rerun
|
|
143
|
+
bindCycleTarget() {
|
|
144
|
+
for (const graph of this.manifest.app.graphs) {
|
|
145
|
+
const activities = graph.activities;
|
|
146
|
+
for (const activityKey in activities) {
|
|
147
|
+
const activity = activities[activityKey];
|
|
148
|
+
if (activity.type === 'cycle') {
|
|
149
|
+
activities[activity.ancestor].cycle = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//it's more intuitive for SDK users to use 'topic',
|
|
155
|
+
//but the compiler is desiged to be generic and uses the attribute, 'subtypes'
|
|
141
156
|
convertTopicsToTypes() {
|
|
142
157
|
for (const graph of this.manifest.app.graphs) {
|
|
143
158
|
const activities = graph.activities;
|
|
@@ -149,6 +164,18 @@ class Deployer {
|
|
|
149
164
|
}
|
|
150
165
|
}
|
|
151
166
|
}
|
|
167
|
+
//legacy; remove at beta (assume no legacy refs to 'activity' at that point)
|
|
168
|
+
convertActivitiesToHooks() {
|
|
169
|
+
for (const graph of this.manifest.app.graphs) {
|
|
170
|
+
const activities = graph.activities;
|
|
171
|
+
for (const activityKey in activities) {
|
|
172
|
+
const activity = activities[activityKey];
|
|
173
|
+
if (['activity'].includes(activity.type)) {
|
|
174
|
+
activity.type = 'hook';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
152
179
|
async bindParents() {
|
|
153
180
|
const graphs = this.manifest.app.graphs;
|
|
154
181
|
for (const graph of graphs) {
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { WorkflowHandleService } from './handle';
|
|
2
2
|
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
3
|
-
import { ClientConfig, Connection, WorkflowOptions } from '../../types/durable';
|
|
3
|
+
import { ClientConfig, Connection, WorkflowOptions, WorkflowSearchOptions } from '../../types/durable';
|
|
4
4
|
export declare class ClientService {
|
|
5
5
|
connection: Connection;
|
|
6
6
|
options: WorkflowOptions;
|
|
7
7
|
static instances: Map<string, HotMesh | Promise<HotMesh>>;
|
|
8
8
|
constructor(config: ClientConfig);
|
|
9
9
|
getHotMeshClient: (worflowTopic: string) => Promise<HotMesh>;
|
|
10
|
+
/**
|
|
11
|
+
* For those deployments with a redis stack backend (with the FT module),
|
|
12
|
+
* this method will configure the search index for the workflow.
|
|
13
|
+
*/
|
|
14
|
+
configureSearchIndex: (hotMeshClient: HotMesh, search?: WorkflowSearchOptions) => Promise<void>;
|
|
15
|
+
search: (hotMeshClient: HotMesh, index: string, query: string[]) => Promise<string[]>;
|
|
10
16
|
workflow: {
|
|
11
17
|
start: (options: WorkflowOptions) => Promise<WorkflowHandleService>;
|
|
12
18
|
signal: (signalId: string, data: Record<any, any>) => Promise<string>;
|
|
13
19
|
getHandle: (taskQueue: string, workflowName: string, workflowId: string) => Promise<WorkflowHandleService>;
|
|
20
|
+
search: (taskQueue: string, workflowName: string, index: string, ...query: string[]) => Promise<string[]>;
|
|
14
21
|
};
|
|
15
22
|
activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
|
|
16
23
|
static shutdown(): Promise<void>;
|