@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.
Files changed (89) hide show
  1. package/README.md +11 -11
  2. package/build/modules/enums.d.ts +1 -0
  3. package/build/modules/enums.js +3 -1
  4. package/build/modules/errors.d.ts +9 -1
  5. package/build/modules/errors.js +12 -1
  6. package/build/modules/key.d.ts +20 -19
  7. package/build/modules/key.js +20 -20
  8. package/build/package.json +1 -1
  9. package/build/services/activities/activity.d.ts +10 -0
  10. package/build/services/activities/activity.js +28 -3
  11. package/build/services/activities/await.js +10 -9
  12. package/build/services/activities/cycle.js +10 -9
  13. package/build/services/activities/hook.d.ts +7 -1
  14. package/build/services/activities/hook.js +61 -44
  15. package/build/services/activities/interrupt.js +10 -9
  16. package/build/services/activities/signal.js +7 -7
  17. package/build/services/activities/trigger.js +4 -2
  18. package/build/services/activities/worker.js +9 -8
  19. package/build/services/durable/meshos.js +2 -2
  20. package/build/services/durable/worker.js +2 -2
  21. package/build/services/durable/workflow.js +17 -17
  22. package/build/services/engine/index.d.ts +5 -7
  23. package/build/services/engine/index.js +53 -47
  24. package/build/services/hotmesh/index.d.ts +2 -2
  25. package/build/services/hotmesh/index.js +6 -7
  26. package/build/services/quorum/index.d.ts +6 -6
  27. package/build/services/quorum/index.js +47 -11
  28. package/build/services/{signaler/stream.d.ts → router/index.d.ts} +3 -3
  29. package/build/services/{signaler/stream.js → router/index.js} +6 -6
  30. package/build/services/serializer/index.js +1 -1
  31. package/build/services/store/clients/ioredis.d.ts +1 -0
  32. package/build/services/store/clients/ioredis.js +9 -0
  33. package/build/services/store/clients/redis.d.ts +1 -0
  34. package/build/services/store/clients/redis.js +16 -0
  35. package/build/services/store/index.d.ts +10 -4
  36. package/build/services/store/index.js +21 -10
  37. package/build/services/stream/clients/ioredis.d.ts +1 -0
  38. package/build/services/stream/clients/ioredis.js +33 -24
  39. package/build/services/stream/clients/redis.d.ts +1 -0
  40. package/build/services/stream/clients/redis.js +15 -0
  41. package/build/services/stream/index.d.ts +1 -0
  42. package/build/services/task/index.d.ts +13 -4
  43. package/build/services/task/index.js +115 -17
  44. package/build/services/telemetry/index.js +6 -6
  45. package/build/services/worker/index.d.ts +4 -3
  46. package/build/services/worker/index.js +32 -8
  47. package/build/types/job.d.ts +2 -0
  48. package/build/types/quorum.d.ts +11 -1
  49. package/build/types/redisclient.d.ts +1 -0
  50. package/build/types/stream.d.ts +1 -0
  51. package/modules/enums.ts +3 -0
  52. package/modules/errors.ts +18 -0
  53. package/modules/key.ts +21 -20
  54. package/package.json +1 -1
  55. package/services/activities/activity.ts +44 -4
  56. package/services/activities/await.ts +14 -10
  57. package/services/activities/cycle.ts +14 -10
  58. package/services/activities/hook.ts +70 -47
  59. package/services/activities/interrupt.ts +13 -10
  60. package/services/activities/signal.ts +11 -8
  61. package/services/activities/trigger.ts +5 -1
  62. package/services/activities/worker.ts +13 -9
  63. package/services/durable/meshos.ts +1 -1
  64. package/services/durable/worker.ts +1 -1
  65. package/services/durable/workflow.ts +1 -1
  66. package/services/engine/index.ts +82 -44
  67. package/services/hotmesh/index.ts +7 -8
  68. package/services/quorum/index.ts +48 -12
  69. package/services/{signaler/stream.ts → router/index.ts} +5 -5
  70. package/services/serializer/index.ts +1 -1
  71. package/services/store/clients/ioredis.ts +9 -0
  72. package/services/store/clients/redis.ts +16 -0
  73. package/services/store/index.ts +27 -12
  74. package/services/stream/clients/ioredis.ts +33 -24
  75. package/services/stream/clients/redis.ts +14 -0
  76. package/services/stream/index.ts +1 -0
  77. package/services/task/index.ts +120 -21
  78. package/services/telemetry/index.ts +6 -6
  79. package/services/worker/index.ts +37 -7
  80. package/types/job.ts +2 -0
  81. package/types/quorum.ts +15 -4
  82. package/types/redisclient.ts +1 -0
  83. package/types/stream.ts +6 -5
  84. package/build/services/signaler/store.d.ts +0 -15
  85. package/build/services/signaler/store.js +0 -68
  86. package/services/signaler/store.ts +0 -76
  87. /package/build/{services/durable/asyncLocalStorage.d.ts → modules/storage.d.ts} +0 -0
  88. /package/build/{services/durable/asyncLocalStorage.js → modules/storage.js} +0 -0
  89. /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,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -1,4 +1,9 @@
1
- import { CollationError, GetStateError, InactiveJobError } from '../../modules/errors';
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
- //assemble list of paths necessary to create 'job state' from the 'symbol hash'
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.streamSignaler?.publishMessage(null, execSignal, multi);
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 { GetStateError, InactiveJobError } from '../../modules/errors';
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
- //confirm entry is allowed and restore state
36
- this.setLeg(1);
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.streamSignaler?.publishMessage(null, streamData, multi)) as string;
103
+ return (await this.engine.router?.publishMessage(null, streamData, multi)) as string;
100
104
  }
101
105
  }
102
106
 
@@ -1,4 +1,7 @@
1
- import { GetStateError, InactiveJobError } from '../../modules/errors';
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
- //verify entry is allowed
35
- this.setLeg(1);
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.streamSignaler?.publishMessage(null, streamData, multi)) as string;
110
+ return (await this.engine.router?.publishMessage(null, streamData, multi)) as string;
107
111
  }
108
112
  }
109
113
 
@@ -1,9 +1,12 @@
1
- import { GetStateError, InactiveJobError } from '../../modules/errors';
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 { StoreSignaler } from '../signaler/store';
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
- * Listens for `webhook`, `timehook`, and `cycle` (repeat) signals
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', { jid: this.context.metadata.jid, aid: this.metadata.aid });
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.setLeg(1);
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.registerHook(multi);
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
- this.adjacencyList = await this.filterAdjacent();
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
- //******** SIGNAL RE-ENTRY POINT ********//
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 signaler = new StoreSignaler(this.store, this.logger);
116
- return await signaler.registerWebHook(this.config.hook.topic, this.context, this.resolveDad(), multi);
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.task.registerTimeHook(jobId, `${activityId}${dId||''}`, 'sleep', durationInSeconds);
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 signaler = new StoreSignaler(this.store, this.logger);
155
+ const taskService = new TaskService(this.store, this.logger);
135
156
  const data = { ...this.data };
136
- const signal = await signaler.processWebHookSignal(this.config.hook.topic, data);
157
+ const signal = await taskService.processWebHookSignal(this.config.hook.topic, data);
137
158
  if (signal) {
138
- const [jobId, aid, dad] = signal;
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) { //otherwise 202 for pending/keepalive
143
- await signaler.deleteWebHookSignal(this.config.hook.topic, data);
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.setLeg(1);
35
- await CollatorService.notarizeEntry(this);
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
- return await this.interruptSelf(telemetry);
43
+ await this.interruptSelf(telemetry);
43
44
  } else {
44
- return await this.interruptAnother(telemetry);
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 { GetStateError, InactiveJobError } from '../../modules/errors';
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
- //verify entry is allowed
37
- this.setLeg(1);
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