@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
@@ -35,6 +35,7 @@ class Trigger extends Activity {
35
35
  try {
36
36
  this.setLeg(2);
37
37
  await this.getState();
38
+
38
39
  telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
39
40
  telemetry.startJobSpan();
40
41
  telemetry.startActivitySpan(this.leg);
@@ -70,7 +71,7 @@ class Trigger extends Activity {
70
71
  } finally {
71
72
  telemetry?.endJobSpan();
72
73
  telemetry?.endActivitySpan();
73
- this.logger.debug('trigger-process-end', { subscribes: this.config.subscribes, jid: this.context.metadata.jid });
74
+ this.logger.debug('trigger-process-end', { subscribes: this.config.subscribes, jid: this.context.metadata.jid, gid: this.context.metadata.gid });
74
75
  }
75
76
  }
76
77
 
@@ -108,8 +109,10 @@ class Trigger extends Activity {
108
109
  this.context = {
109
110
  metadata: {
110
111
  ...this.metadata,
112
+ gid: guid(),
111
113
  ngn: this.context.metadata.ngn,
112
114
  pj: this.context.metadata.pj,
115
+ pg: this.context.metadata.pg,
113
116
  pd: this.context.metadata.pd,
114
117
  pa: this.context.metadata.pa,
115
118
  app: id,
@@ -187,6 +190,7 @@ class Trigger extends Activity {
187
190
  resolvedDepKey,
188
191
  this.context.metadata.tpc,
189
192
  this.context.metadata.jid,
193
+ this.context.metadata.gid,
190
194
  multi,
191
195
  );
192
196
  }
@@ -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 } from './activity';
3
6
  import { CollatorService } from '../collator';
4
7
  import { EngineService } from '../engine';
@@ -29,14 +32,11 @@ class Worker extends Activity {
29
32
 
30
33
  //******** INITIAL ENTRY POINT (A) ********//
31
34
  async process(): Promise<string> {
32
- this.logger.debug('worker-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
35
+ this.logger.debug('worker-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();
@@ -63,6 +63,9 @@ class Worker extends Activity {
63
63
  if (error instanceof InactiveJobError) {
64
64
  this.logger.error('await-inactive-job-error', { error });
65
65
  return;
66
+ } else if (error instanceof GenerationalError) {
67
+ this.logger.info('process-event-generational-job-error', { error });
68
+ return;
66
69
  } else if (error instanceof GetStateError) {
67
70
  this.logger.error('worker-get-state-error', { error });
68
71
  return;
@@ -73,7 +76,7 @@ class Worker extends Activity {
73
76
  throw error;
74
77
  } finally {
75
78
  telemetry?.endActivitySpan();
76
- this.logger.debug('worker-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
79
+ this.logger.debug('worker-process-end', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
77
80
  }
78
81
  }
79
82
 
@@ -83,6 +86,7 @@ class Worker extends Activity {
83
86
  metadata: {
84
87
  guid: guid(),
85
88
  jid: this.context.metadata.jid,
89
+ gid: this.context.metadata.gid,
86
90
  dad: this.metadata.dad,
87
91
  aid: this.metadata.aid,
88
92
  topic,
@@ -96,7 +100,7 @@ class Worker extends Activity {
96
100
  retry: this.config.retry
97
101
  };
98
102
  }
99
- return (await this.engine.streamSignaler?.publishMessage(topic, streamData, multi)) as string;
103
+ return (await this.engine.router?.publishMessage(topic, streamData, multi)) as string;
100
104
  }
101
105
  }
102
106
 
@@ -1,5 +1,5 @@
1
1
  import { Durable } from '.';
2
- import { asyncLocalStorage } from './asyncLocalStorage';
2
+ import { asyncLocalStorage } from '../../modules/storage';
3
3
  import { ClientService as Client } from './client';
4
4
  import { WorkflowHandleService } from './handle';
5
5
  import { Search } from './search';
@@ -7,7 +7,7 @@ import {
7
7
  DurableSleepForError,
8
8
  DurableTimeoutError,
9
9
  DurableWaitForSignalError} from '../../modules/errors';
10
- import { asyncLocalStorage } from './asyncLocalStorage';
10
+ import { asyncLocalStorage } from '../../modules/storage';
11
11
  import { APP_ID, APP_VERSION, getWorkflowYAML } from './factory';
12
12
  import { HotMeshService as HotMesh } from '../hotmesh';
13
13
  import {
@@ -6,7 +6,7 @@ import {
6
6
  DurableSleepForError,
7
7
  DurableWaitForSignalError } from '../../modules/errors';
8
8
  import { KeyService, KeyType } from '../../modules/key';
9
- import { asyncLocalStorage } from './asyncLocalStorage';
9
+ import { asyncLocalStorage } from '../../modules/storage';
10
10
  import { ClientService as Client } from './client';
11
11
  import { ConnectionService as Connection } from './connection';
12
12
  import { DEFAULT_COEFFICIENT } from './factory';
@@ -4,7 +4,7 @@ import {
4
4
  STATUS_CODE_SUCCESS,
5
5
  STATUS_CODE_PENDING,
6
6
  STATUS_CODE_TIMEOUT,
7
- DURABLE_EXPIRE_SECONDS} from '../../modules/enums';
7
+ DURABLE_EXPIRE_SECONDS } from '../../modules/enums';
8
8
  import {
9
9
  formatISODate,
10
10
  getSubscriptionTopic,
@@ -23,9 +23,8 @@ import { Trigger } from '../activities/trigger';
23
23
  import { CompilerService } from '../compiler';
24
24
  import { ILogger } from '../logger';
25
25
  import { ReporterService } from '../reporter';
26
+ import { Router } from '../router';
26
27
  import { SerializerService } from '../serializer';
27
- import { StoreSignaler } from '../signaler/store';
28
- import { StreamSignaler } from '../signaler/stream';
29
28
  import { StoreService } from '../store';
30
29
  import { RedisStoreService as RedisStore } from '../store/clients/redis';
31
30
  import { IORedisStoreService as IORedisStore } from '../store/clients/ioredis';
@@ -84,12 +83,11 @@ class EngineService {
84
83
  apps: HotMeshApps | null;
85
84
  appId: string;
86
85
  guid: string;
86
+ router: Router | null;
87
87
  store: StoreService<RedisClient, RedisMulti> | null;
88
88
  stream: StreamService<RedisClient, RedisMulti> | null;
89
89
  subscribe: SubService<RedisClient, RedisMulti> | null;
90
- storeSignaler: StoreSignaler | null;
91
- streamSignaler: StreamSignaler | null;
92
- task: TaskService | null;
90
+ taskService: TaskService | null;
93
91
  logger: ILogger;
94
92
  cacheMode: CacheMode = 'cache';
95
93
  untilVersion: string | null = null;
@@ -110,9 +108,9 @@ class EngineService {
110
108
  await instance.initStoreChannel(config.engine.store);
111
109
  await instance.initSubChannel(config.engine.sub);
112
110
  await instance.initStreamChannel(config.engine.stream);
113
- instance.streamSignaler = instance.initStreamSignaler(config);
111
+ instance.router = instance.initRouter(config);
114
112
 
115
- instance.streamSignaler.consumeMessages(
113
+ instance.router.consumeMessages(
116
114
  instance.stream.mintKey(
117
115
  KeyType.STREAMS,
118
116
  { appId: instance.appId },
@@ -122,12 +120,8 @@ class EngineService {
122
120
  instance.processStreamMessage.bind(instance)
123
121
  );
124
122
 
125
- //the storeSignaler service is used by the engine to create `webhooks`
126
- //todo: unify/move to the task service (it manages all `signal` types)
127
- instance.storeSignaler = new StoreSignaler(instance.store, logger);
128
-
129
123
  //the task service is used by the engine to process `webhooks` and `timehooks`
130
- instance.task = new TaskService(instance.store, logger);
124
+ instance.taskService = new TaskService(instance.store, logger);
131
125
 
132
126
  return instance;
133
127
  }
@@ -181,8 +175,8 @@ class EngineService {
181
175
  );
182
176
  }
183
177
 
184
- initStreamSignaler(config: HotMeshConfig): StreamSignaler {
185
- return new StreamSignaler(
178
+ initRouter(config: HotMeshConfig): Router {
179
+ return new Router(
186
180
  {
187
181
  namespace: this.namespace,
188
182
  appId: this.appId,
@@ -231,21 +225,20 @@ class EngineService {
231
225
  }
232
226
 
233
227
  async processWebHooks() {
234
- this.task.processWebHooks((this.hook).bind(this));
228
+ this.taskService.processWebHooks((this.hook).bind(this));
235
229
  }
236
230
 
237
231
  async processTimeHooks() {
238
- this.task.processTimeHooks((this.hookTime).bind(this));
232
+ this.taskService.processTimeHooks((this.hookTime).bind(this));
239
233
  }
240
234
 
241
235
  async throttle(delayInMillis: number) {
242
- this.streamSignaler.setThrottle(delayInMillis);
236
+ this.router.setThrottle(delayInMillis);
243
237
  }
244
238
 
245
239
  // ************* METADATA/MODEL METHODS *************
246
240
  async initActivity(topic: string, data: JobData = {}, context?: JobState): Promise<Await|Cycle|Hook|Signal|Trigger|Worker|Interrupt> {
247
241
  const [activityId, schema] = await this.getSchema(topic);
248
- polyfill
249
242
  const ActivityHandler = Activities[polyfill.resolveActivityType(schema.type)];
250
243
  if (ActivityHandler) {
251
244
  const utc = formatISODate(new Date());
@@ -329,6 +322,7 @@ class EngineService {
329
322
  async processStreamMessage(streamData: StreamDataResponse): Promise<void> {
330
323
  this.logger.debug('engine-process-stream-message', {
331
324
  jid: streamData.metadata.jid,
325
+ gid: streamData.metadata.gid,
332
326
  dad: streamData.metadata.dad,
333
327
  aid: streamData.metadata.aid,
334
328
  status: streamData.status || StreamStatus.SUCCESS,
@@ -338,41 +332,83 @@ class EngineService {
338
332
  const context: PartialJobState = {
339
333
  metadata: {
340
334
  jid: streamData.metadata.jid,
335
+ gid: streamData.metadata.gid,
341
336
  dad: streamData.metadata.dad,
342
337
  aid: streamData.metadata.aid,
343
338
  },
344
339
  data: streamData.data,
345
340
  };
346
- if (streamData.type === StreamDataType.TIMEHOOK || streamData.type === StreamDataType.WEBHOOK || streamData.type === StreamDataType.TRANSITION) {
347
- const activityHandler = await this.initActivity(`.${streamData.metadata.aid}`, context.data, context as JobState) as Hook;
348
- if (streamData.type === StreamDataType.TIMEHOOK) {
349
- await activityHandler.processTimeHookEvent(streamData.metadata.jid);
350
- } else if (streamData.type === StreamDataType.TRANSITION) {
351
- await activityHandler.process();
352
- } else {
353
- //a 202 code keeps the hook alive (hooks are single-use by default)
354
- await activityHandler.processWebHookEvent(streamData.status, streamData.code);
355
- }
341
+ if (streamData.type === StreamDataType.TIMEHOOK) {
342
+ //TIMEHOOK AWAKEN
343
+ const activityHandler = await this.initActivity(
344
+ `.${streamData.metadata.aid}`,
345
+ context.data,
346
+ context as JobState,
347
+ ) as Hook;
348
+ await activityHandler.processTimeHookEvent(streamData.metadata.jid);
349
+ } else if (streamData.type === StreamDataType.WEBHOOK) {
350
+ //WEBHOOK AWAKEN (SIGNAL IN)
351
+ const activityHandler = await this.initActivity(
352
+ `.${streamData.metadata.aid}`,
353
+ context.data,
354
+ context as JobState,
355
+ ) as Hook;
356
+ await activityHandler.processWebHookEvent(
357
+ streamData.status,
358
+ streamData.code
359
+ );
360
+ } else if (streamData.type === StreamDataType.TRANSITION) {
361
+ //TRANSITION (ADJACENT ACTIVITY)
362
+ const activityHandler = await this.initActivity(
363
+ `.${streamData.metadata.aid}`,
364
+ context.data,
365
+ context as JobState,
366
+ ) as Hook; //todo: `as Activity` (type is more generic)
367
+ await activityHandler.process();
356
368
  } else if (streamData.type === StreamDataType.AWAIT) {
369
+ //TRIGGER JOB
357
370
  context.metadata = {
358
371
  ...context.metadata,
359
372
  pj: streamData.metadata.jid,
373
+ pg: streamData.metadata.gid,
360
374
  pd: streamData.metadata.dad,
361
375
  pa: streamData.metadata.aid,
362
376
  trc: streamData.metadata.trc,
363
377
  spn: streamData.metadata.spn,
364
378
  };
365
- const activityHandler = await this.initActivity(streamData.metadata.topic, streamData.data, context as JobState) as Trigger;
379
+ const activityHandler = await this.initActivity(
380
+ streamData.metadata.topic,
381
+ streamData.data,
382
+ context as JobState
383
+ ) as Trigger;
366
384
  await activityHandler.process();
367
385
  } else if (streamData.type === StreamDataType.RESULT) {
368
- const activityHandler = await this.initActivity(`.${context.metadata.aid}`, streamData.data, context as JobState) as Await;
369
- await activityHandler.processEvent(streamData.status, streamData.code);
386
+ //AWAIT RESULT
387
+ const activityHandler = await this.initActivity(
388
+ `.${context.metadata.aid}`,
389
+ streamData.data,
390
+ context as JobState,
391
+ ) as Await;
392
+ await activityHandler.processEvent(
393
+ streamData.status,
394
+ streamData.code,
395
+ );
370
396
  } else {
371
- const activityHandler = await this.initActivity(`.${streamData.metadata.aid}`, streamData.data, context as JobState) as Worker;
372
- await activityHandler.processEvent(streamData.status, streamData.code, 'output');
397
+ //WORKER RESULT
398
+ const activityHandler = await this.initActivity(
399
+ `.${streamData.metadata.aid}`,
400
+ streamData.data,
401
+ context as JobState,
402
+ ) as Worker;
403
+ await activityHandler.processEvent(
404
+ streamData.status,
405
+ streamData.code,
406
+ 'output'
407
+ );
373
408
  }
374
409
  this.logger.debug('engine-process-stream-message-end', {
375
410
  jid: streamData.metadata.jid,
411
+ gid: streamData.metadata.gid,
376
412
  aid: streamData.metadata.aid
377
413
  });
378
414
  }
@@ -387,6 +423,7 @@ class EngineService {
387
423
  metadata: {
388
424
  guid: guid(),
389
425
  jid: context.metadata.pj,
426
+ gid: context.metadata.pg,
390
427
  dad: context.metadata.pd,
391
428
  aid: context.metadata.pa,
392
429
  trc: context.metadata.trc,
@@ -406,7 +443,7 @@ class EngineService {
406
443
  streamData.status = StreamStatus.SUCCESS;
407
444
  streamData.code = STATUS_CODE_SUCCESS;
408
445
  }
409
- return (await this.streamSignaler?.publishMessage(null, streamData)) as string;
446
+ return (await this.router?.publishMessage(null, streamData)) as string;
410
447
  }
411
448
  }
412
449
  hasParentJob(context: JobState): boolean {
@@ -437,7 +474,7 @@ class EngineService {
437
474
 
438
475
  // ****************** `HOOK` ACTIVITY RE-ENTRY POINT *****************
439
476
  async hook(topic: string, data: JobData, status: StreamStatus = StreamStatus.SUCCESS, code: StreamCode = 200): Promise<string> {
440
- const hookRule = await this.storeSignaler.getHookRule(topic);
477
+ const hookRule = await this.taskService.getHookRule(topic);
441
478
  const [aid] = await this.getSchema(`.${hookRule.to}`);
442
479
  const streamData: StreamData = {
443
480
  type: StreamDataType.WEBHOOK,
@@ -450,9 +487,9 @@ class EngineService {
450
487
  },
451
488
  data,
452
489
  };
453
- return await this.streamSignaler.publishMessage(null, streamData) as string;
490
+ return await this.router.publishMessage(null, streamData) as string;
454
491
  }
455
- async hookTime(jobId: string, activityId: string, type?: 'sleep'|'expire'|'interrupt'): Promise<string | void> {
492
+ async hookTime(jobId: string, gId: string, activityId: string, type?: 'sleep'|'expire'|'interrupt'): Promise<string | void> {
456
493
  if (type === 'interrupt') {
457
494
  return await this.interrupt(
458
495
  activityId, //note: 'activityId' is the actually job topic
@@ -470,16 +507,17 @@ class EngineService {
470
507
  metadata: {
471
508
  guid: guid(),
472
509
  jid: jobId,
473
- aid,
510
+ gid: gId,
474
511
  dad,
512
+ aid,
475
513
  },
476
514
  data: { timestamp: Date.now() },
477
515
  };
478
- await this.streamSignaler.publishMessage(null, streamData);
516
+ await this.router.publishMessage(null, streamData);
479
517
  }
480
518
  async hookAll(hookTopic: string, data: JobData, keyResolver: JobStatsInput, queryFacets: string[] = []): Promise<string[]> {
481
519
  const config = await this.getVID();
482
- const hookRule = await this.storeSignaler.getHookRule(hookTopic);
520
+ const hookRule = await this.taskService.getHookRule(hookTopic);
483
521
  if (hookRule) {
484
522
  const subscriptionTopic = await getSubscriptionTopic(hookRule.to, this.store, config)
485
523
  const resolvedQuery = await this.resolveQuery(subscriptionTopic, keyResolver);
@@ -597,7 +635,7 @@ class EngineService {
597
635
  }
598
636
  }
599
637
  async add(streamData: StreamData|StreamDataResponse): Promise<string> {
600
- return await this.streamSignaler.publishMessage(null, streamData) as string;
638
+ return await this.router.publishMessage(null, streamData) as string;
601
639
  }
602
640
 
603
641
  registerJobCallback(jobId: string, jobCallback: JobMessageCallback) {
@@ -632,7 +670,7 @@ class EngineService {
632
670
  this.pubPermSubs(context, jobOutput, options.emit);
633
671
  }
634
672
  if (!options.emit) {
635
- this.task.registerJobForCleanup(
673
+ this.taskService.registerJobForCleanup(
636
674
  context.metadata.jid,
637
675
  this.resolveExpires(context, options),
638
676
  options,
@@ -647,7 +685,7 @@ class EngineService {
647
685
  * it will be expired immediately.
648
686
  */
649
687
  resolveExpires(context: JobState, options: JobCompletionOptions): number {
650
- return context.metadata.expire ?? options.expire ?? DURABLE_EXPIRE_SECONDS;
688
+ return options.expire ?? context.metadata.expire ?? DURABLE_EXPIRE_SECONDS;
651
689
  }
652
690
 
653
691
 
@@ -4,8 +4,8 @@ import { RedisConnection } from '../connector/clients/redis';
4
4
  import { RedisConnection as IORedisConnection } from '../connector/clients/ioredis';
5
5
  import { EngineService } from '../engine';
6
6
  import { LoggerService, ILogger } from '../logger';
7
- import { StreamSignaler } from '../signaler/stream';
8
7
  import { QuorumService } from '../quorum';
8
+ import { Router } from '../router';
9
9
  import { WorkerService } from '../worker';
10
10
  import {
11
11
  JobState,
@@ -16,7 +16,7 @@ import {
16
16
  import {
17
17
  HotMeshConfig,
18
18
  HotMeshManifest } from '../../types/hotmesh';
19
- import { JobMessageCallback } from '../../types/quorum';
19
+ import { JobMessageCallback, QuorumProfile } from '../../types/quorum';
20
20
  import {
21
21
  JobStatsInput,
22
22
  GetStatsOptions,
@@ -137,6 +137,9 @@ class HotMeshService {
137
137
  }
138
138
 
139
139
  // ************* COMPILER METHODS *************
140
+ async rollCall(delay?: number): Promise<QuorumProfile[]> {
141
+ return await this.quorum?.rollCall(delay);
142
+ }
140
143
  async plan(path: string): Promise<HotMeshManifest> {
141
144
  return await this.engine?.plan(path);
142
145
  }
@@ -147,10 +150,6 @@ class HotMeshService {
147
150
  //activation is a quorum operation
148
151
  return await this.quorum?.activate(version, delay);
149
152
  }
150
- async inventory(version: string, delay?: number): Promise<number> {
151
- //get count of all peers
152
- return await this.quorum?.inventory(delay);
153
- }
154
153
 
155
154
  // ************* REPORTER METHODS *************
156
155
  async getStats(topic: string, query: JobStatsInput): Promise<StatsResponse> {
@@ -193,14 +192,14 @@ class HotMeshService {
193
192
  static async stop() {
194
193
  if (!this.disconnecting) {
195
194
  this.disconnecting = true;
196
- await StreamSignaler.stopConsuming();
195
+ await Router.stopConsuming();
197
196
  await RedisConnection.disconnectAll();
198
197
  await IORedisConnection.disconnectAll();
199
198
  }
200
199
  }
201
200
 
202
201
  stop() {
203
- this.engine?.task.cancelCleanup();
202
+ this.engine?.taskService.cancelCleanup();
204
203
  }
205
204
 
206
205
  async compress(terms: string[]): Promise<boolean> {
@@ -14,6 +14,7 @@ import { RedisClientType as IORedisClientType } from '../../types/ioredisclient'
14
14
  import {
15
15
  QuorumMessage,
16
16
  QuorumMessageCallback,
17
+ QuorumProfile,
17
18
  SubscriptionCallback,
18
19
  ThrottleMessage
19
20
  } from '../../types/quorum';
@@ -26,10 +27,10 @@ const QUORUM_DELAY = 250;
26
27
 
27
28
  class QuorumService {
28
29
  namespace: string;
29
- apps: HotMeshApps | null;
30
30
  appId: string;
31
31
  guid: string;
32
32
  engine: EngineService;
33
+ profiles: QuorumProfile[] = [];
33
34
  store: StoreService<RedisClient, RedisMulti> | null;
34
35
  subscribe: SubService<RedisClient, RedisMulti> | null;
35
36
  logger: ILogger;
@@ -108,9 +109,12 @@ class QuorumService {
108
109
  if (message.type === 'activate') {
109
110
  self.engine.setCacheMode(message.cache_mode, message.until_version);
110
111
  } else if (message.type === 'ping') {
111
- this.sayPong(self.appId, self.guid, message.originator);
112
+ self.sayPong(self.appId, self.guid, message.originator, message.details);
112
113
  } else if (message.type === 'pong' && self.guid === message.originator) {
113
114
  self.quorum = self.quorum + 1;
115
+ if (message.profile) {
116
+ self.profiles.push(message.profile);
117
+ }
114
118
  } else if (message.type === 'throttle') {
115
119
  self.engine.throttle(message.throttle);
116
120
  } else if (message.type === 'work') {
@@ -127,20 +131,38 @@ class QuorumService {
127
131
  };
128
132
  }
129
133
 
130
- async sayPong(appId: string, guid: string, originator: string) {
134
+ async sayPong(appId: string, guid: string, originator: string, details = false) {
135
+ let profile: QuorumProfile;
136
+ if (details) {
137
+ profile = {
138
+ engine_id: this.guid,
139
+ namespace: this.namespace,
140
+ app_id: this.appId,
141
+ stream: this.engine.stream.mintKey(KeyType.STREAMS, { appId: this.appId })
142
+ };
143
+ }
131
144
  this.store.publish(
132
145
  KeyType.QUORUM,
133
- { type: 'pong', guid, originator },
146
+ {
147
+ type: 'pong',
148
+ guid, originator,
149
+ profile,
150
+ },
134
151
  appId,
135
152
  );
136
153
  }
137
154
 
138
- async requestQuorum(delay = QUORUM_DELAY): Promise<number> {
155
+ async requestQuorum(delay = QUORUM_DELAY, details = false): Promise<number> {
139
156
  const quorum = this.quorum;
140
157
  this.quorum = 0;
158
+ this.profiles.length = 0;
141
159
  await this.store.publish(
142
160
  KeyType.QUORUM,
143
- { type: 'ping', originator: this.guid },
161
+ {
162
+ type: 'ping',
163
+ originator: this.guid,
164
+ details,
165
+ },
144
166
  this.appId,
145
167
  );
146
168
  await sleepFor(delay);
@@ -166,12 +188,26 @@ class QuorumService {
166
188
 
167
189
 
168
190
  // ************* COMPILER METHODS *************
169
- async inventory(delay = QUORUM_DELAY): Promise<number> {
170
- await this.requestQuorum(delay);
171
- const q1 = await this.requestQuorum(delay);
172
- const q2 = await this.requestQuorum(delay);
173
- const q3 = await this.requestQuorum(delay);
174
- return Math.round((q1 + q2 + q3) / 3);
191
+ async rollCall(delay = QUORUM_DELAY): Promise<QuorumProfile[]> {
192
+ await this.requestQuorum(delay, true);
193
+ const targetStreams = [];
194
+ const multi = this.store.getMulti();
195
+ this.profiles.forEach((profile: QuorumProfile) => {
196
+ if (!targetStreams.includes(profile.stream)) {
197
+ targetStreams.push(profile.stream);
198
+ this.store.xlen(profile.stream, multi);
199
+ }
200
+ });
201
+ const stream_depths = await multi.exec() as number[];
202
+ this.profiles.forEach(async (profile: QuorumProfile) => {
203
+ const index = targetStreams.indexOf(profile.stream);
204
+ if (index != -1) {
205
+ profile.stream_depth = Array.isArray(stream_depths[index]) ?
206
+ stream_depths[index][1] :
207
+ stream_depths[index];
208
+ }
209
+ });
210
+ return this.profiles;
175
211
  }
176
212
  async activate(version: string, delay = QUORUM_DELAY): Promise<boolean> {
177
213
  version = version.toString();
@@ -27,8 +27,8 @@ import {
27
27
  StreamStatus
28
28
  } from '../../types/stream';
29
29
 
30
- class StreamSignaler {
31
- static signalers: Set<StreamSignaler> = new Set();
30
+ class Router {
31
+ static instances: Set<Router> = new Set();
32
32
  appId: string;
33
33
  guid: string;
34
34
  role: StreamRole;
@@ -70,7 +70,7 @@ class StreamSignaler {
70
70
 
71
71
  async consumeMessages(stream: string, group: string, consumer: string, callback: (streamData: StreamData) => Promise<StreamDataResponse|void>): Promise<void> {
72
72
  this.logger.info(`stream-consumer-starting`, { group, consumer, stream });
73
- StreamSignaler.signalers.add(this);
73
+ Router.instances.add(this);
74
74
  this.shouldConsume = true;
75
75
  await this.createGroup(stream, group);
76
76
  let lastCheckedPendingMessagesAt = Date.now();
@@ -254,7 +254,7 @@ class StreamSignaler {
254
254
  }
255
255
 
256
256
  static async stopConsuming() {
257
- for (const instance of [...StreamSignaler.signalers]) {
257
+ for (const instance of [...Router.instances]) {
258
258
  instance.stopConsuming();
259
259
  }
260
260
  await sleepFor(BLOCK_TIME_MS);
@@ -356,4 +356,4 @@ class StreamSignaler {
356
356
  }
357
357
  }
358
358
 
359
- export { StreamSignaler };
359
+ export { Router };
@@ -19,7 +19,7 @@ export const MDATA_SYMBOLS = {
19
19
  KEYS: ['au', 'err', 'l2s']
20
20
  },
21
21
  JOB: {
22
- KEYS: ['ngn', 'tpc', 'pj', 'pd', 'pa', 'key', 'app', 'vrs', 'jid', 'aid', 'ts', 'jc', 'ju', 'js', 'err', 'trc']
22
+ KEYS: ['ngn', 'tpc', 'pj', 'pg', 'pd', 'pa', 'key', 'app', 'vrs', 'jid', 'gid', 'aid', 'ts', 'jc', 'ju', 'js', 'err', 'trc']
23
23
  },
24
24
  JOB_UPDATE: {
25
25
  KEYS: ['ju', 'err']
@@ -131,6 +131,15 @@ class IORedisStoreService extends StoreService<RedisClientType, RedisMultiType>
131
131
  throw error;
132
132
  }
133
133
  }
134
+
135
+ async xlen(key: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
136
+ try {
137
+ return await (multi || this.redisClient).xlen(key);
138
+ } catch (error) {
139
+ this.logger.error(`Error getting stream depth: ${key}`, { error });
140
+ throw error;
141
+ }
142
+ }
134
143
  }
135
144
 
136
145
  export { IORedisStoreService };
@@ -42,6 +42,7 @@ class RedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
42
42
  rpush: 'RPUSH',
43
43
  xack: 'XACK',
44
44
  xdel: 'XDEL',
45
+ xlen: 'XLEN',
45
46
  };
46
47
  }
47
48
 
@@ -167,6 +168,21 @@ class RedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
167
168
  throw error;
168
169
  }
169
170
  }
171
+
172
+ async xlen(key: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
173
+ try {
174
+ if (multi) {
175
+ multi.XLEN(key);
176
+ return multi;
177
+ } else {
178
+ return await this.redisClient.XLEN(key);
179
+ }
180
+ } catch (error) {
181
+ this.logger.error(`Error getting stream depth: ${key}`, { error });
182
+ throw error;
183
+ }
184
+ }
185
+
170
186
  }
171
187
 
172
188
  export { RedisStoreService };