@hotmeshio/hotmesh 0.0.36 → 0.0.38
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/README.md +11 -11
- package/build/modules/enums.d.ts +1 -0
- package/build/modules/enums.js +3 -1
- package/build/modules/errors.d.ts +9 -1
- package/build/modules/errors.js +12 -1
- package/build/modules/key.d.ts +20 -19
- package/build/modules/key.js +20 -20
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +10 -0
- package/build/services/activities/activity.js +28 -3
- package/build/services/activities/await.js +10 -9
- package/build/services/activities/cycle.js +10 -9
- package/build/services/activities/hook.d.ts +7 -1
- package/build/services/activities/hook.js +61 -44
- package/build/services/activities/interrupt.js +10 -9
- package/build/services/activities/signal.js +7 -7
- package/build/services/activities/trigger.js +4 -2
- package/build/services/activities/worker.js +9 -8
- package/build/services/durable/meshos.js +2 -2
- package/build/services/durable/worker.js +2 -2
- package/build/services/durable/workflow.js +17 -17
- package/build/services/engine/index.d.ts +5 -7
- package/build/services/engine/index.js +53 -47
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +6 -7
- package/build/services/quorum/index.d.ts +6 -6
- package/build/services/quorum/index.js +47 -11
- package/build/services/{signaler/stream.d.ts → router/index.d.ts} +3 -3
- package/build/services/{signaler/stream.js → router/index.js} +6 -6
- package/build/services/serializer/index.js +1 -1
- package/build/services/store/clients/ioredis.d.ts +1 -0
- package/build/services/store/clients/ioredis.js +9 -0
- package/build/services/store/clients/redis.d.ts +1 -0
- package/build/services/store/clients/redis.js +16 -0
- package/build/services/store/index.d.ts +10 -4
- package/build/services/store/index.js +21 -10
- package/build/services/stream/clients/ioredis.d.ts +1 -0
- package/build/services/stream/clients/ioredis.js +33 -24
- package/build/services/stream/clients/redis.d.ts +1 -0
- package/build/services/stream/clients/redis.js +15 -0
- package/build/services/stream/index.d.ts +1 -0
- package/build/services/task/index.d.ts +13 -4
- package/build/services/task/index.js +115 -17
- package/build/services/telemetry/index.js +6 -6
- package/build/services/worker/index.d.ts +4 -3
- package/build/services/worker/index.js +32 -8
- package/build/types/job.d.ts +2 -0
- package/build/types/quorum.d.ts +11 -1
- package/build/types/redisclient.d.ts +1 -0
- package/build/types/stream.d.ts +1 -0
- package/modules/enums.ts +3 -0
- package/modules/errors.ts +18 -0
- package/modules/key.ts +21 -20
- package/package.json +1 -1
- package/services/activities/activity.ts +44 -4
- package/services/activities/await.ts +14 -10
- package/services/activities/cycle.ts +14 -10
- package/services/activities/hook.ts +70 -47
- package/services/activities/interrupt.ts +13 -10
- package/services/activities/signal.ts +11 -8
- package/services/activities/trigger.ts +5 -1
- package/services/activities/worker.ts +13 -9
- package/services/durable/meshos.ts +1 -1
- package/services/durable/worker.ts +1 -1
- package/services/durable/workflow.ts +1 -1
- package/services/engine/index.ts +82 -44
- package/services/hotmesh/index.ts +7 -8
- package/services/quorum/index.ts +48 -12
- package/services/{signaler/stream.ts → router/index.ts} +5 -5
- package/services/serializer/index.ts +1 -1
- package/services/store/clients/ioredis.ts +9 -0
- package/services/store/clients/redis.ts +16 -0
- package/services/store/index.ts +27 -12
- package/services/stream/clients/ioredis.ts +33 -24
- package/services/stream/clients/redis.ts +14 -0
- package/services/stream/index.ts +1 -0
- package/services/task/index.ts +120 -21
- package/services/telemetry/index.ts +6 -6
- package/services/worker/index.ts +37 -7
- package/types/job.ts +2 -0
- package/types/quorum.ts +15 -4
- package/types/redisclient.ts +1 -0
- package/types/stream.ts +6 -5
- package/build/services/signaler/store.d.ts +0 -15
- package/build/services/signaler/store.js +0 -68
- package/services/signaler/store.ts +0 -76
- /package/build/{services/durable/asyncLocalStorage.d.ts → modules/storage.d.ts} +0 -0
- /package/build/{services/durable/asyncLocalStorage.js → modules/storage.js} +0 -0
- /package/{services/durable/asyncLocalStorage.ts → modules/storage.ts} +0 -0
package/modules/errors.ts
CHANGED
|
@@ -122,6 +122,23 @@ class InactiveJobError extends Error {
|
|
|
122
122
|
this.status = status;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
class GenerationalError extends Error {
|
|
126
|
+
expected: string;
|
|
127
|
+
actual: string;
|
|
128
|
+
jobId: string;
|
|
129
|
+
activityId: string;
|
|
130
|
+
dimensionalAddress: string;
|
|
131
|
+
|
|
132
|
+
constructor(expected: string, actual: string, jobId: string, activityId: string, dimensionalAddress: string) {
|
|
133
|
+
super("Generational Error");
|
|
134
|
+
this.expected = expected;
|
|
135
|
+
this.actual = actual;
|
|
136
|
+
this.jobId = jobId;
|
|
137
|
+
this.activityId = activityId;
|
|
138
|
+
this.dimensionalAddress = dimensionalAddress;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
125
142
|
class ExecActivityError extends Error {
|
|
126
143
|
constructor() {
|
|
127
144
|
super("Error occurred while executing activity");
|
|
@@ -155,6 +172,7 @@ export {
|
|
|
155
172
|
DurableWaitForSignalError,
|
|
156
173
|
DuplicateJobError,
|
|
157
174
|
ExecActivityError,
|
|
175
|
+
GenerationalError,
|
|
158
176
|
GetStateError,
|
|
159
177
|
InactiveJobError,
|
|
160
178
|
MapDataError,
|
package/modules/key.ts
CHANGED
|
@@ -31,25 +31,25 @@ const HMNS = "hmsh";
|
|
|
31
31
|
|
|
32
32
|
//these are the entity types that are stored in the key/value store
|
|
33
33
|
enum KeyType {
|
|
34
|
-
APP,
|
|
35
|
-
ENGINE_ID,
|
|
36
|
-
HOOKS,
|
|
37
|
-
JOB_DEPENDENTS,
|
|
38
|
-
JOB_STATE,
|
|
39
|
-
JOB_STATS_GENERAL,
|
|
40
|
-
JOB_STATS_MEDIAN,
|
|
41
|
-
JOB_STATS_INDEX,
|
|
42
|
-
HOTMESH,
|
|
43
|
-
QUORUM,
|
|
44
|
-
SCHEMAS,
|
|
45
|
-
SIGNALS,
|
|
46
|
-
STREAMS,
|
|
47
|
-
SUBSCRIPTIONS,
|
|
48
|
-
SUBSCRIPTION_PATTERNS,
|
|
49
|
-
SYMKEYS,
|
|
50
|
-
SYMVALS,
|
|
51
|
-
TIME_RANGE,
|
|
52
|
-
WORK_ITEMS,
|
|
34
|
+
APP = 'APP',
|
|
35
|
+
ENGINE_ID = 'ENGINE',
|
|
36
|
+
HOOKS = 'HOOKS',
|
|
37
|
+
JOB_DEPENDENTS = 'JOB_DEPENDENTS',
|
|
38
|
+
JOB_STATE = 'JOB_STATE',
|
|
39
|
+
JOB_STATS_GENERAL = 'JOB_STATS_GENERAL',
|
|
40
|
+
JOB_STATS_MEDIAN = 'JOB_STATS_MEDIAN',
|
|
41
|
+
JOB_STATS_INDEX = 'JOB_STATS_INDEX',
|
|
42
|
+
HOTMESH = 'HOTMESH',
|
|
43
|
+
QUORUM = 'QUORUM',
|
|
44
|
+
SCHEMAS = 'SCHEMAS',
|
|
45
|
+
SIGNALS = 'SIGNALS',
|
|
46
|
+
STREAMS = 'STREAMS',
|
|
47
|
+
SUBSCRIPTIONS = 'SUBSCRIPTIONS',
|
|
48
|
+
SUBSCRIPTION_PATTERNS = 'SUBSCRIPTION_PATTERNS',
|
|
49
|
+
SYMKEYS = 'SYMKEYS',
|
|
50
|
+
SYMVALS = 'SYMVALS',
|
|
51
|
+
TIME_RANGE = 'TIME_RANGE',
|
|
52
|
+
WORK_ITEMS = 'WORK_ITEMS',
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
//when minting a key, the following parameters are used to create a unique key per entity
|
|
@@ -64,6 +64,7 @@ type KeyStoreParams = {
|
|
|
64
64
|
facet?: string; //data path starting at root with values separated by colons (e.g. "object/type:bar")
|
|
65
65
|
topic?: string; //topic name (e.g., "foo" or "" for top-level)
|
|
66
66
|
timeValue?: number; //time value (rounded to minute) (for delete range)
|
|
67
|
+
scoutType?: 'signal' | 'time'; //a single member of the quorum serves as the 'scout' for the group, triaging tasks for the collective
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
class KeyService {
|
|
@@ -86,7 +87,7 @@ class KeyService {
|
|
|
86
87
|
case KeyType.ENGINE_ID:
|
|
87
88
|
return `${namespace}:${params.appId}:e:${params.engineId}`;
|
|
88
89
|
case KeyType.WORK_ITEMS:
|
|
89
|
-
return `${namespace}:${params.appId}:w
|
|
90
|
+
return `${namespace}:${params.appId}:w:${params.scoutType || ''}`;
|
|
90
91
|
case KeyType.TIME_RANGE:
|
|
91
92
|
return `${namespace}:${params.appId}:t:${params.timeValue || ''}`;
|
|
92
93
|
case KeyType.APP:
|
package/package.json
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EXPIRE_DURATION } from '../../modules/enums';
|
|
2
|
+
import {
|
|
3
|
+
CollationError,
|
|
4
|
+
GenerationalError,
|
|
5
|
+
GetStateError,
|
|
6
|
+
InactiveJobError } from '../../modules/errors';
|
|
2
7
|
import {
|
|
3
8
|
formatISODate,
|
|
4
9
|
getValueByPath,
|
|
@@ -30,7 +35,6 @@ import {
|
|
|
30
35
|
StreamDataType,
|
|
31
36
|
StreamStatus } from '../../types/stream';
|
|
32
37
|
import { TransitionRule } from '../../types/transition';
|
|
33
|
-
import { EXPIRE_DURATION } from '../../modules/enums';
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
40
|
* The base class for all activities
|
|
@@ -71,6 +75,21 @@ class Activity {
|
|
|
71
75
|
this.leg = leg;
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Upon entering leg 1 of a duplexed activty, verify
|
|
80
|
+
* all aspects of the entry including job and activty state
|
|
81
|
+
*/
|
|
82
|
+
async verifyEntry() {
|
|
83
|
+
this.setLeg(1);
|
|
84
|
+
await this.getState();
|
|
85
|
+
CollatorService.assertJobActive(
|
|
86
|
+
this.context.metadata.js,
|
|
87
|
+
this.context.metadata.jid,
|
|
88
|
+
this.metadata.aid
|
|
89
|
+
);
|
|
90
|
+
await CollatorService.notarizeEntry(this);
|
|
91
|
+
}
|
|
92
|
+
|
|
74
93
|
//******** DUPLEX RE-ENTRY POINT ********//
|
|
75
94
|
async processEvent(status: StreamStatus = StreamStatus.SUCCESS, code: StreamCode = 200, type: 'hook' | 'output' = 'output'): Promise<void> {
|
|
76
95
|
this.setLeg(2);
|
|
@@ -116,6 +135,9 @@ class Activity {
|
|
|
116
135
|
} else if (error instanceof InactiveJobError) {
|
|
117
136
|
this.logger.info('process-event-inactive-job-error', { error });
|
|
118
137
|
return;
|
|
138
|
+
} else if (error instanceof GenerationalError) {
|
|
139
|
+
this.logger.info('process-event-generational-job-error', { error });
|
|
140
|
+
return;
|
|
119
141
|
} else if (error instanceof GetStateError) {
|
|
120
142
|
this.logger.info('process-event-get-job-error', { error });
|
|
121
143
|
return;
|
|
@@ -337,7 +359,7 @@ class Activity {
|
|
|
337
359
|
}
|
|
338
360
|
|
|
339
361
|
async getState() {
|
|
340
|
-
|
|
362
|
+
const gid = this.context.metadata.gid;
|
|
341
363
|
const jobSymbolHashName = `$${this.config.subscribes}`;
|
|
342
364
|
const consumes: Consumes = {
|
|
343
365
|
[jobSymbolHashName]: MDATA_SYMBOLS.JOB.KEYS.map((key) => `metadata/${key}`)
|
|
@@ -365,11 +387,28 @@ class Activity {
|
|
|
365
387
|
//`state` is a flat hash; context is a tree
|
|
366
388
|
const [state, status] = await this.store.getState(jid, consumes, dIds);
|
|
367
389
|
this.context = restoreHierarchy(state) as JobState;
|
|
390
|
+
this.assertGenerationalId(this.context.metadata.gid, gid);
|
|
368
391
|
this.initDimensionalAddress(dad);
|
|
369
392
|
this.initSelf(this.context);
|
|
370
393
|
this.initPolicies(this.context);
|
|
371
394
|
}
|
|
372
395
|
|
|
396
|
+
/**
|
|
397
|
+
* if the job is created/deleted/created with the same key,
|
|
398
|
+
* the 'gid' ensures no stale messages enter the stream
|
|
399
|
+
*/
|
|
400
|
+
assertGenerationalId(jobGID: string, msgGID?: string) {
|
|
401
|
+
if (msgGID !== jobGID) {
|
|
402
|
+
throw new GenerationalError(
|
|
403
|
+
jobGID,
|
|
404
|
+
msgGID,
|
|
405
|
+
this.context.metadata.jid,
|
|
406
|
+
this.context.metadata.aid,
|
|
407
|
+
this.context.metadata.dad
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
373
412
|
initDimensionalAddress(dad: string): void {
|
|
374
413
|
this.metadata.dad = dad;
|
|
375
414
|
}
|
|
@@ -434,6 +473,7 @@ class Activity {
|
|
|
434
473
|
metadata: {
|
|
435
474
|
guid: guid(),
|
|
436
475
|
jid: this.context.metadata.jid,
|
|
476
|
+
gid: this.context.metadata.gid,
|
|
437
477
|
dad: adjacentDad,
|
|
438
478
|
aid: toActivityId,
|
|
439
479
|
spn: this.context['$self'].output.metadata?.l2s,
|
|
@@ -466,7 +506,7 @@ class Activity {
|
|
|
466
506
|
if (adjacencyList.length && jobStatus > 0) {
|
|
467
507
|
const multi = this.store.getMulti();
|
|
468
508
|
for (const execSignal of adjacencyList) {
|
|
469
|
-
await this.engine.
|
|
509
|
+
await this.engine.router?.publishMessage(null, execSignal, multi);
|
|
470
510
|
}
|
|
471
511
|
mIds = (await multi.exec()) as string[];
|
|
472
512
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GenerationalError,
|
|
3
|
+
GetStateError,
|
|
4
|
+
InactiveJobError } from '../../modules/errors';
|
|
2
5
|
import { Activity } from './activity';
|
|
3
6
|
import { CollatorService } from '../collator';
|
|
4
7
|
import { EngineService } from '../engine';
|
|
8
|
+
import { TelemetryService } from '../telemetry';
|
|
5
9
|
import {
|
|
6
10
|
ActivityData,
|
|
7
11
|
ActivityMetadata,
|
|
@@ -10,7 +14,6 @@ import {
|
|
|
10
14
|
import { JobState } from '../../types/job';
|
|
11
15
|
import { MultiResponseFlags, RedisMulti } from '../../types/redis';
|
|
12
16
|
import { StreamData, StreamDataType } from '../../types/stream';
|
|
13
|
-
import { TelemetryService } from '../telemetry';
|
|
14
17
|
import { Pipe } from '../pipe';
|
|
15
18
|
import { guid } from '../../modules/utils';
|
|
16
19
|
|
|
@@ -29,14 +32,11 @@ class Await extends Activity {
|
|
|
29
32
|
|
|
30
33
|
//******** INITIAL ENTRY POINT (A) ********//
|
|
31
34
|
async process(): Promise<string> {
|
|
32
|
-
this.logger.debug('await-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
35
|
+
this.logger.debug('await-process', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
33
36
|
let telemetry: TelemetryService;
|
|
34
37
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await CollatorService.notarizeEntry(this);
|
|
38
|
-
await this.getState();
|
|
39
|
-
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
38
|
+
await this.verifyEntry();
|
|
39
|
+
|
|
40
40
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
41
41
|
telemetry.startActivitySpan(this.leg);
|
|
42
42
|
this.mapInputData();
|
|
@@ -62,6 +62,9 @@ class Await extends Activity {
|
|
|
62
62
|
if (error instanceof InactiveJobError) {
|
|
63
63
|
this.logger.error('await-inactive-job-error', { error });
|
|
64
64
|
return;
|
|
65
|
+
} else if (error instanceof GenerationalError) {
|
|
66
|
+
this.logger.info('process-event-generational-job-error', { error });
|
|
67
|
+
return;
|
|
65
68
|
} else if (error instanceof GetStateError) {
|
|
66
69
|
this.logger.error('await-get-state-error', { error });
|
|
67
70
|
return;
|
|
@@ -72,7 +75,7 @@ class Await extends Activity {
|
|
|
72
75
|
throw error;
|
|
73
76
|
} finally {
|
|
74
77
|
telemetry?.endActivitySpan();
|
|
75
|
-
this.logger.debug('await-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
78
|
+
this.logger.debug('await-process-end', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -82,6 +85,7 @@ class Await extends Activity {
|
|
|
82
85
|
metadata: {
|
|
83
86
|
guid: guid(),
|
|
84
87
|
jid: this.context.metadata.jid,
|
|
88
|
+
gid: this.context.metadata.gid,
|
|
85
89
|
dad: this.metadata.dad,
|
|
86
90
|
aid: this.metadata.aid,
|
|
87
91
|
topic,
|
|
@@ -96,7 +100,7 @@ class Await extends Activity {
|
|
|
96
100
|
retry: this.config.retry
|
|
97
101
|
};
|
|
98
102
|
}
|
|
99
|
-
return (await this.engine.
|
|
103
|
+
return (await this.engine.router?.publishMessage(null, streamData, multi)) as string;
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GenerationalError,
|
|
3
|
+
GetStateError,
|
|
4
|
+
InactiveJobError } from '../../modules/errors';
|
|
2
5
|
import { CollatorService } from '../collator';
|
|
3
6
|
import { EngineService } from '../engine';
|
|
4
7
|
import { Activity, ActivityType } from './activity';
|
|
@@ -28,14 +31,11 @@ class Cycle extends Activity {
|
|
|
28
31
|
|
|
29
32
|
//******** LEG 1 ENTRY ********//
|
|
30
33
|
async process(): Promise<string> {
|
|
31
|
-
this.logger.debug('cycle-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
34
|
+
this.logger.debug('cycle-process', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
32
35
|
let telemetry: TelemetryService;
|
|
33
36
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
await CollatorService.notarizeEntry(this);
|
|
37
|
-
await this.getState();
|
|
38
|
-
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
37
|
+
await this.verifyEntry();
|
|
38
|
+
|
|
39
39
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
40
40
|
telemetry.startActivitySpan(this.leg);
|
|
41
41
|
this.mapInputData();
|
|
@@ -65,6 +65,9 @@ class Cycle extends Activity {
|
|
|
65
65
|
if (error instanceof InactiveJobError) {
|
|
66
66
|
this.logger.error('cycle-inactive-job-error', { error });
|
|
67
67
|
return;
|
|
68
|
+
} else if (error instanceof GenerationalError) {
|
|
69
|
+
this.logger.info('process-event-generational-job-error', { error });
|
|
70
|
+
return;
|
|
68
71
|
} else if (error instanceof GetStateError) {
|
|
69
72
|
this.logger.error('cycle-get-state-error', { error });
|
|
70
73
|
return;
|
|
@@ -75,7 +78,7 @@ class Cycle extends Activity {
|
|
|
75
78
|
throw error;
|
|
76
79
|
} finally {
|
|
77
80
|
telemetry?.endActivitySpan();
|
|
78
|
-
this.logger.debug('cycle-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
81
|
+
this.logger.debug('cycle-process-end', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -95,15 +98,16 @@ class Cycle extends Activity {
|
|
|
95
98
|
const streamData: StreamData = {
|
|
96
99
|
metadata: {
|
|
97
100
|
guid: guid(),
|
|
98
|
-
dad: CollatorService.resolveReentryDimension(this),
|
|
99
101
|
jid: this.context.metadata.jid,
|
|
102
|
+
gid: this.context.metadata.gid,
|
|
103
|
+
dad: CollatorService.resolveReentryDimension(this),
|
|
100
104
|
aid: this.config.ancestor,
|
|
101
105
|
spn: this.context['$self'].output.metadata?.l1s,
|
|
102
106
|
trc: this.context.metadata.trc,
|
|
103
107
|
},
|
|
104
108
|
data: this.context.data
|
|
105
109
|
};
|
|
106
|
-
return (await this.engine.
|
|
110
|
+
return (await this.engine.router?.publishMessage(null, streamData, multi)) as string;
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GenerationalError,
|
|
3
|
+
GetStateError,
|
|
4
|
+
InactiveJobError } from '../../modules/errors';
|
|
2
5
|
import { Activity } from './activity';
|
|
3
6
|
import { CollatorService } from '../collator';
|
|
4
7
|
import { EngineService } from '../engine';
|
|
5
8
|
import { Pipe } from '../pipe';
|
|
6
|
-
import {
|
|
9
|
+
import { TaskService } from '../task';
|
|
7
10
|
import { TelemetryService } from '../telemetry';
|
|
8
11
|
import {
|
|
9
12
|
ActivityData,
|
|
@@ -19,7 +22,7 @@ import { StringScalarType } from '../../types/serializer';
|
|
|
19
22
|
import { StreamCode, StreamStatus } from '../../types/stream';
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
*
|
|
25
|
+
* Supports `signal hook`, `time hook`, and `cycle hook` patterns
|
|
23
26
|
*/
|
|
24
27
|
class Hook extends Activity {
|
|
25
28
|
config: HookActivity;
|
|
@@ -36,48 +39,25 @@ class Hook extends Activity {
|
|
|
36
39
|
|
|
37
40
|
//******** INITIAL ENTRY POINT (A) ********//
|
|
38
41
|
async process(): Promise<string> {
|
|
39
|
-
this.logger.debug('hook-process', {
|
|
42
|
+
this.logger.debug('hook-process', {
|
|
43
|
+
jid: this.context.metadata.jid,
|
|
44
|
+
gid: this.context.metadata.gid,
|
|
45
|
+
aid: this.metadata.aid,
|
|
46
|
+
});
|
|
40
47
|
let telemetry: TelemetryService;
|
|
48
|
+
|
|
41
49
|
try {
|
|
42
|
-
this.
|
|
43
|
-
await CollatorService.notarizeEntry(this);
|
|
50
|
+
await this.verifyEntry();
|
|
44
51
|
|
|
45
|
-
await this.getState();
|
|
46
|
-
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
47
52
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
48
53
|
telemetry.startActivitySpan(this.leg);
|
|
49
|
-
let multiResponse: MultiResponseFlags;
|
|
50
54
|
|
|
51
|
-
const multi = this.store.getMulti();
|
|
52
55
|
if (this.doesHook()) {
|
|
53
56
|
//sleep and wait to awaken upon a signal
|
|
54
|
-
await this.
|
|
55
|
-
this.mapOutputData();
|
|
56
|
-
this.mapJobData();
|
|
57
|
-
await this.setState(multi);
|
|
58
|
-
await CollatorService.authorizeReentry(this, multi);
|
|
59
|
-
|
|
60
|
-
await this.setStatus(0, multi);
|
|
61
|
-
await multi.exec();
|
|
62
|
-
telemetry.mapActivityAttributes();
|
|
57
|
+
await this.doHook(telemetry);
|
|
63
58
|
} else {
|
|
64
59
|
//end the activity and transition to its children
|
|
65
|
-
|
|
66
|
-
this.mapOutputData();
|
|
67
|
-
this.mapJobData();
|
|
68
|
-
await this.setState(multi);
|
|
69
|
-
await CollatorService.notarizeEarlyCompletion(this, multi);
|
|
70
|
-
|
|
71
|
-
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
72
|
-
multiResponse = await multi.exec() as MultiResponseFlags;
|
|
73
|
-
telemetry.mapActivityAttributes();
|
|
74
|
-
const jobStatus = this.resolveStatus(multiResponse);
|
|
75
|
-
const attrs: StringScalarType = { 'app.job.jss': jobStatus };
|
|
76
|
-
const messageIds = await this.transition(this.adjacencyList, jobStatus);
|
|
77
|
-
if (messageIds.length) {
|
|
78
|
-
attrs['app.activity.mids'] = messageIds.join(',')
|
|
79
|
-
}
|
|
80
|
-
telemetry.setActivityAttributes(attrs);
|
|
60
|
+
await this.doPassThrough(telemetry);
|
|
81
61
|
}
|
|
82
62
|
|
|
83
63
|
return this.context.metadata.aid;
|
|
@@ -85,6 +65,9 @@ class Hook extends Activity {
|
|
|
85
65
|
if (error instanceof InactiveJobError) {
|
|
86
66
|
this.logger.error('hook-inactive-job-error', { error });
|
|
87
67
|
return;
|
|
68
|
+
} else if (error instanceof GenerationalError) {
|
|
69
|
+
this.logger.info('process-event-generational-job-error', { error });
|
|
70
|
+
return;
|
|
88
71
|
} else if (error instanceof GetStateError) {
|
|
89
72
|
this.logger.error('hook-get-state-error', { error });
|
|
90
73
|
return;
|
|
@@ -95,16 +78,52 @@ class Hook extends Activity {
|
|
|
95
78
|
throw error;
|
|
96
79
|
} finally {
|
|
97
80
|
telemetry?.endActivitySpan();
|
|
98
|
-
this.logger.debug('hook-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
81
|
+
this.logger.debug('hook-process-end', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
99
82
|
}
|
|
100
83
|
}
|
|
101
84
|
|
|
102
|
-
|
|
85
|
+
/**
|
|
86
|
+
* does this activity use a time-hook or web-hook
|
|
87
|
+
*/
|
|
103
88
|
doesHook(): boolean {
|
|
104
|
-
//does this activity use a time-hook or web-hook
|
|
105
89
|
return !!(this.config.hook?.topic || this.config.sleep);
|
|
106
90
|
}
|
|
107
91
|
|
|
92
|
+
async doHook(telemetry: TelemetryService) {
|
|
93
|
+
const multi = this.store.getMulti();
|
|
94
|
+
await this.registerHook(multi);
|
|
95
|
+
this.mapOutputData();
|
|
96
|
+
this.mapJobData();
|
|
97
|
+
await this.setState(multi);
|
|
98
|
+
await CollatorService.authorizeReentry(this, multi);
|
|
99
|
+
|
|
100
|
+
await this.setStatus(0, multi);
|
|
101
|
+
await multi.exec();
|
|
102
|
+
telemetry.mapActivityAttributes();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async doPassThrough(telemetry: TelemetryService) {
|
|
106
|
+
const multi = this.store.getMulti();
|
|
107
|
+
let multiResponse: MultiResponseFlags;
|
|
108
|
+
|
|
109
|
+
this.adjacencyList = await this.filterAdjacent();
|
|
110
|
+
this.mapOutputData();
|
|
111
|
+
this.mapJobData();
|
|
112
|
+
await this.setState(multi);
|
|
113
|
+
await CollatorService.notarizeEarlyCompletion(this, multi);
|
|
114
|
+
|
|
115
|
+
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
116
|
+
multiResponse = await multi.exec() as MultiResponseFlags;
|
|
117
|
+
telemetry.mapActivityAttributes();
|
|
118
|
+
const jobStatus = this.resolveStatus(multiResponse);
|
|
119
|
+
const attrs: StringScalarType = { 'app.job.jss': jobStatus };
|
|
120
|
+
const messageIds = await this.transition(this.adjacencyList, jobStatus);
|
|
121
|
+
if (messageIds.length) {
|
|
122
|
+
attrs['app.activity.mids'] = messageIds.join(',')
|
|
123
|
+
}
|
|
124
|
+
telemetry.setActivityAttributes(attrs);
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
async getHookRule(topic: string): Promise<HookRule | undefined> {
|
|
109
128
|
const rules = await this.store.getHookRules();
|
|
110
129
|
return rules?.[topic]?.[0] as HookRule;
|
|
@@ -112,18 +131,20 @@ class Hook extends Activity {
|
|
|
112
131
|
|
|
113
132
|
async registerHook(multi?: RedisMulti): Promise<string | void> {
|
|
114
133
|
if (this.config.hook?.topic) {
|
|
115
|
-
const
|
|
116
|
-
return await
|
|
134
|
+
const taskService = new TaskService(this.store, this.logger);
|
|
135
|
+
return await taskService.registerWebHook(this.config.hook.topic, this.context, this.resolveDad(), multi);
|
|
117
136
|
} else if (this.config.sleep) {
|
|
118
137
|
const durationInSeconds = Pipe.resolve(this.config.sleep, this.context);
|
|
119
138
|
const jobId = this.context.metadata.jid;
|
|
139
|
+
const gId = this.context.metadata.gid;
|
|
120
140
|
const activityId = this.metadata.aid;
|
|
121
141
|
const dId = this.metadata.dad;
|
|
122
|
-
await this.engine.
|
|
142
|
+
await this.engine.taskService.registerTimeHook(jobId, gId, `${activityId}${dId||''}`, 'sleep', durationInSeconds);
|
|
123
143
|
return jobId;
|
|
124
144
|
}
|
|
125
145
|
}
|
|
126
146
|
|
|
147
|
+
//******** SIGNAL RE-ENTRY POINT ********//
|
|
127
148
|
async processWebHookEvent(status: StreamStatus = StreamStatus.SUCCESS, code: StreamCode = 200): Promise<JobStatus | void> {
|
|
128
149
|
this.logger.debug('hook-process-web-hook-event', {
|
|
129
150
|
topic: this.config.hook.topic,
|
|
@@ -131,23 +152,25 @@ class Hook extends Activity {
|
|
|
131
152
|
status,
|
|
132
153
|
code,
|
|
133
154
|
});
|
|
134
|
-
const
|
|
155
|
+
const taskService = new TaskService(this.store, this.logger);
|
|
135
156
|
const data = { ...this.data };
|
|
136
|
-
const signal = await
|
|
157
|
+
const signal = await taskService.processWebHookSignal(this.config.hook.topic, data);
|
|
137
158
|
if (signal) {
|
|
138
|
-
const [jobId,
|
|
159
|
+
const [jobId, _aid, dad, gId] = signal;
|
|
139
160
|
this.context.metadata.jid = jobId;
|
|
161
|
+
this.context.metadata.gid = gId;
|
|
140
162
|
this.context.metadata.dad = dad;
|
|
141
163
|
await this.processEvent(status, code, 'hook');
|
|
142
|
-
if (code === 200) {
|
|
143
|
-
await
|
|
144
|
-
}
|
|
164
|
+
if (code === 200) {
|
|
165
|
+
await taskService.deleteWebHookSignal(this.config.hook.topic, data);
|
|
166
|
+
} //else => 202/keep alive
|
|
145
167
|
} //else => already resolved
|
|
146
168
|
}
|
|
147
169
|
|
|
148
170
|
async processTimeHookEvent(jobId: string): Promise<JobStatus | void> {
|
|
149
171
|
this.logger.debug('hook-process-time-hook-event', {
|
|
150
172
|
jid: jobId,
|
|
173
|
+
gid: this.context.metadata.gid,
|
|
151
174
|
aid: this.metadata.aid
|
|
152
175
|
});
|
|
153
176
|
await this.processEvent(StreamStatus.SUCCESS, 200, 'hook');
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GenerationalError,
|
|
3
|
+
GetStateError,
|
|
4
|
+
InactiveJobError } from '../../modules/errors';
|
|
1
5
|
import { EngineService } from '../engine';
|
|
2
6
|
import { Activity, ActivityType } from './activity';
|
|
3
7
|
import {
|
|
4
8
|
ActivityData,
|
|
5
9
|
ActivityMetadata,
|
|
6
10
|
InterruptActivity } from '../../types/activity';
|
|
7
|
-
import { GetStateError, InactiveJobError } from '../../modules/errors';
|
|
8
11
|
import { MultiResponseFlags } from '../../types';
|
|
9
12
|
import { CollatorService } from '../collator';
|
|
10
13
|
import { JobInterruptOptions, JobState } from '../../types/job';
|
|
@@ -28,25 +31,26 @@ class Interrupt extends Activity {
|
|
|
28
31
|
|
|
29
32
|
//******** LEG 1 ENTRY ********//
|
|
30
33
|
async process(): Promise<string> {
|
|
31
|
-
this.logger.debug('interrupt-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
34
|
+
this.logger.debug('interrupt-process', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
32
35
|
let telemetry: TelemetryService;
|
|
33
36
|
try {
|
|
34
|
-
this.
|
|
35
|
-
|
|
36
|
-
await this.getState();
|
|
37
|
-
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid); // Ensure job active
|
|
37
|
+
await this.verifyEntry();
|
|
38
|
+
|
|
38
39
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
39
40
|
telemetry.startActivitySpan(this.leg);
|
|
40
41
|
|
|
41
42
|
if (this.isInterruptingSelf()) {
|
|
42
|
-
|
|
43
|
+
await this.interruptSelf(telemetry);
|
|
43
44
|
} else {
|
|
44
|
-
|
|
45
|
+
await this.interruptAnother(telemetry);
|
|
45
46
|
}
|
|
46
47
|
} catch (error) {
|
|
47
48
|
if (error instanceof InactiveJobError) {
|
|
48
49
|
this.logger.error('interrupt-inactive-job-error', { error });
|
|
49
50
|
return;
|
|
51
|
+
} else if (error instanceof GenerationalError) {
|
|
52
|
+
this.logger.info('process-event-generational-job-error', { error });
|
|
53
|
+
return;
|
|
50
54
|
} else if (error instanceof GetStateError) {
|
|
51
55
|
this.logger.error('interrupt-get-state-error', { error });
|
|
52
56
|
return;
|
|
@@ -57,7 +61,7 @@ class Interrupt extends Activity {
|
|
|
57
61
|
throw error;
|
|
58
62
|
} finally {
|
|
59
63
|
telemetry?.endActivitySpan();
|
|
60
|
-
this.logger.debug('interrupt-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
64
|
+
this.logger.debug('interrupt-process-end', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
|
|
@@ -118,7 +122,6 @@ class Interrupt extends Activity {
|
|
|
118
122
|
|
|
119
123
|
return this.context.metadata.aid;
|
|
120
124
|
}
|
|
121
|
-
|
|
122
125
|
|
|
123
126
|
isInterruptingSelf(): boolean {
|
|
124
127
|
if (!this.config.target) {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GenerationalError,
|
|
3
|
+
GetStateError,
|
|
4
|
+
InactiveJobError } from '../../modules/errors';
|
|
2
5
|
import { Activity, ActivityType } from './activity';
|
|
3
6
|
import { CollatorService } from '../collator';
|
|
4
7
|
import { EngineService } from '../engine';
|
|
@@ -30,14 +33,11 @@ class Signal extends Activity {
|
|
|
30
33
|
|
|
31
34
|
//******** LEG 1 ENTRY ********//
|
|
32
35
|
async process(): Promise<string> {
|
|
33
|
-
this.logger.debug('signal-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
36
|
+
this.logger.debug('signal-process', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
34
37
|
let telemetry: TelemetryService;
|
|
35
38
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
await CollatorService.notarizeEntry(this);
|
|
39
|
-
await this.getState();
|
|
40
|
-
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
39
|
+
await this.verifyEntry();
|
|
40
|
+
|
|
41
41
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
42
42
|
telemetry.startActivitySpan(this.leg);
|
|
43
43
|
|
|
@@ -73,6 +73,9 @@ class Signal extends Activity {
|
|
|
73
73
|
if (error instanceof InactiveJobError) {
|
|
74
74
|
this.logger.error('signal-inactive-job-error', { error });
|
|
75
75
|
return;
|
|
76
|
+
} else if (error instanceof GenerationalError) {
|
|
77
|
+
this.logger.info('process-event-generational-job-error', { error });
|
|
78
|
+
return;
|
|
76
79
|
} else if (error instanceof GetStateError) {
|
|
77
80
|
this.logger.error('signal-get-state-error', { error });
|
|
78
81
|
return;
|
|
@@ -83,7 +86,7 @@ class Signal extends Activity {
|
|
|
83
86
|
throw error;
|
|
84
87
|
} finally {
|
|
85
88
|
telemetry?.endActivitySpan();
|
|
86
|
-
this.logger.debug('signal-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
89
|
+
this.logger.debug('signal-process-end', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
|