@hotmeshio/hotmesh 0.0.37 → 0.0.39

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 (78) hide show
  1. package/README.md +14 -8
  2. package/build/modules/enums.d.ts +29 -23
  3. package/build/modules/enums.js +38 -29
  4. package/build/modules/errors.d.ts +1 -1
  5. package/build/modules/errors.js +9 -7
  6. package/build/modules/key.d.ts +1 -34
  7. package/build/modules/key.js +24 -47
  8. package/build/package.json +1 -1
  9. package/build/services/activities/activity.js +1 -1
  10. package/build/services/activities/hook.js +4 -9
  11. package/build/services/activities/trigger.d.ts +3 -2
  12. package/build/services/activities/trigger.js +10 -6
  13. package/build/services/durable/client.d.ts +9 -1
  14. package/build/services/durable/client.js +30 -14
  15. package/build/services/durable/handle.js +2 -2
  16. package/build/services/durable/worker.js +4 -3
  17. package/build/services/engine/index.d.ts +2 -1
  18. package/build/services/engine/index.js +6 -6
  19. package/build/services/hotmesh/index.d.ts +2 -2
  20. package/build/services/hotmesh/index.js +3 -4
  21. package/build/services/quorum/index.d.ts +6 -6
  22. package/build/services/quorum/index.js +47 -11
  23. package/build/services/router/index.js +16 -14
  24. package/build/services/store/clients/ioredis.d.ts +1 -0
  25. package/build/services/store/clients/ioredis.js +9 -0
  26. package/build/services/store/clients/redis.d.ts +1 -0
  27. package/build/services/store/clients/redis.js +16 -0
  28. package/build/services/store/index.d.ts +15 -9
  29. package/build/services/store/index.js +46 -23
  30. package/build/services/stream/clients/ioredis.d.ts +1 -0
  31. package/build/services/stream/clients/ioredis.js +33 -24
  32. package/build/services/stream/clients/redis.d.ts +1 -0
  33. package/build/services/stream/clients/redis.js +15 -0
  34. package/build/services/stream/index.d.ts +1 -0
  35. package/build/services/task/index.d.ts +10 -3
  36. package/build/services/task/index.js +35 -17
  37. package/build/services/worker/index.d.ts +1 -0
  38. package/build/services/worker/index.js +24 -0
  39. package/build/types/durable.d.ts +3 -2
  40. package/build/types/hotmesh.d.ts +43 -2
  41. package/build/types/hotmesh.js +28 -0
  42. package/build/types/index.d.ts +3 -2
  43. package/build/types/index.js +3 -1
  44. package/build/types/logger.d.ts +1 -0
  45. package/build/types/logger.js +1 -0
  46. package/build/types/quorum.d.ts +11 -1
  47. package/build/types/redisclient.d.ts +1 -0
  48. package/build/types/task.d.ts +1 -0
  49. package/build/types/task.js +2 -0
  50. package/modules/enums.ts +49 -35
  51. package/modules/errors.ts +17 -8
  52. package/modules/key.ts +3 -40
  53. package/package.json +1 -1
  54. package/services/activities/activity.ts +2 -2
  55. package/services/activities/hook.ts +18 -9
  56. package/services/activities/trigger.ts +10 -6
  57. package/services/durable/client.ts +31 -15
  58. package/services/durable/handle.ts +3 -3
  59. package/services/durable/worker.ts +4 -3
  60. package/services/engine/index.ts +13 -12
  61. package/services/hotmesh/index.ts +4 -5
  62. package/services/quorum/index.ts +48 -12
  63. package/services/router/index.ts +26 -24
  64. package/services/store/clients/ioredis.ts +9 -0
  65. package/services/store/clients/redis.ts +16 -0
  66. package/services/store/index.ts +63 -25
  67. package/services/stream/clients/ioredis.ts +33 -24
  68. package/services/stream/clients/redis.ts +14 -0
  69. package/services/stream/index.ts +1 -0
  70. package/services/task/index.ts +66 -24
  71. package/services/worker/index.ts +30 -0
  72. package/types/durable.ts +6 -5
  73. package/types/hotmesh.ts +47 -2
  74. package/types/index.ts +8 -1
  75. package/types/logger.ts +3 -1
  76. package/types/quorum.ts +15 -4
  77. package/types/redisclient.ts +1 -0
  78. package/types/task.ts +1 -0
@@ -8,6 +8,7 @@ const factory_1 = require("./factory");
8
8
  const hotmesh_1 = require("../hotmesh");
9
9
  const search_1 = require("./search");
10
10
  const stream_1 = require("../../types/stream");
11
+ const enums_1 = require("../../modules/enums");
11
12
  class WorkerService {
12
13
  static async activateWorkflow(hotMesh) {
13
14
  const app = await hotMesh.engine.store.getApp(hotMesh.engine.appId);
@@ -85,7 +86,7 @@ class WorkerService {
85
86
  options: config.connection.options
86
87
  };
87
88
  const hotMeshWorker = await hotmesh_1.HotMeshService.init({
88
- logLevel: config.options?.logLevel ?? 'info',
89
+ logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
89
90
  appId: config.namespace ?? factory_1.APP_ID,
90
91
  engine: { redis: redisConfig },
91
92
  workers: [
@@ -134,7 +135,7 @@ class WorkerService {
134
135
  options: config.connection.options
135
136
  };
136
137
  const hotMeshWorker = await hotmesh_1.HotMeshService.init({
137
- logLevel: config.options?.logLevel ?? 'info',
138
+ logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
138
139
  appId: config.namespace ?? factory_1.APP_ID,
139
140
  engine: { redis: redisConfig },
140
141
  workers: [{
@@ -260,7 +261,7 @@ WorkerService.getHotMesh = async (workflowTopic, config, options) => {
260
261
  return await WorkerService.instances.get(workflowTopic);
261
262
  }
262
263
  const hotMeshClient = hotmesh_1.HotMeshService.init({
263
- logLevel: options?.logLevel ?? 'info',
264
+ logLevel: options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
264
265
  appId: config.namespace ?? factory_1.APP_ID,
265
266
  engine: { redis: { ...WorkerService.connection } }
266
267
  });
@@ -21,6 +21,7 @@ import { RedisClient, RedisMulti } from '../../types/redis';
21
21
  import { StringAnyType } from '../../types/serializer';
22
22
  import { GetStatsOptions, IdsResponse, JobStatsInput, StatsResponse } from '../../types/stats';
23
23
  import { StreamCode, StreamData, StreamDataResponse, StreamError, StreamStatus } from '../../types/stream';
24
+ import { WorkListTaskType } from '../../types/task';
24
25
  declare class EngineService {
25
26
  namespace: string;
26
27
  apps: HotMeshApps | null;
@@ -65,7 +66,7 @@ declare class EngineService {
65
66
  interrupt(topic: string, jobId: string, options?: JobInterruptOptions): Promise<string>;
66
67
  scrub(jobId: string): Promise<void>;
67
68
  hook(topic: string, data: JobData, status?: StreamStatus, code?: StreamCode): Promise<string>;
68
- hookTime(jobId: string, gId: string, activityId: string, type?: 'sleep' | 'expire' | 'interrupt'): Promise<string | void>;
69
+ hookTime(jobId: string, gId: string, activityId: string, type?: WorkListTaskType): Promise<string | void>;
69
70
  hookAll(hookTopic: string, data: JobData, keyResolver: JobStatsInput, queryFacets?: string[]): Promise<string[]>;
70
71
  pub(topic: string, data: JobData, context?: JobState): Promise<string>;
71
72
  sub(topic: string, callback: JobMessageCallback): Promise<void>;
@@ -304,11 +304,11 @@ class EngineService {
304
304
  }
305
305
  else if (emit) {
306
306
  streamData.status = stream_1.StreamStatus.PENDING;
307
- streamData.code = enums_1.STATUS_CODE_PENDING;
307
+ streamData.code = enums_1.HMSH_CODE_PENDING;
308
308
  }
309
309
  else {
310
310
  streamData.status = stream_1.StreamStatus.SUCCESS;
311
- streamData.code = enums_1.STATUS_CODE_SUCCESS;
311
+ streamData.code = enums_1.HMSH_CODE_SUCCESS;
312
312
  }
313
313
  return (await this.router?.publishMessage(null, streamData));
314
314
  }
@@ -361,7 +361,6 @@ class EngineService {
361
361
  else if (type === 'expire') {
362
362
  return await this.store.expireJob(jobId, 1);
363
363
  }
364
- //'sleep': parse the activityId into parts
365
364
  const [aid, ...dimensions] = activityId.split(',');
366
365
  const dad = `,${dimensions.join(',')}`;
367
366
  const streamData = {
@@ -430,7 +429,7 @@ class EngineService {
430
429
  return await this.subscribe.punsubscribe(key_1.KeyType.QUORUM, this.appId, wild);
431
430
  }
432
431
  //publish and await (returns the job and data (if ready)); throws error with jobid if not
433
- async pubsub(topic, data, context, timeout = enums_1.OTT_WAIT_TIME) {
432
+ async pubsub(topic, data, context, timeout = enums_1.HMSH_OTT_WAIT_TIME) {
434
433
  context = {
435
434
  metadata: {
436
435
  ngn: this.guid,
@@ -453,9 +452,10 @@ class EngineService {
453
452
  }
454
453
  });
455
454
  setTimeout(() => {
455
+ //note: job is still active (the subscriber timed out)
456
456
  this.delistJobCallback(jobId);
457
457
  reject({
458
- code: enums_1.STATUS_CODE_TIMEOUT,
458
+ code: enums_1.HMSH_CODE_TIMEOUT,
459
459
  message: 'timeout',
460
460
  job_id: jobId
461
461
  });
@@ -526,7 +526,7 @@ class EngineService {
526
526
  * it will be expired immediately.
527
527
  */
528
528
  resolveExpires(context, options) {
529
- return options.expire ?? context.metadata.expire ?? enums_1.DURABLE_EXPIRE_SECONDS;
529
+ return options.expire ?? context.metadata.expire ?? enums_1.HMSH_EXPIRE_JOB_SECONDS;
530
530
  }
531
531
  // ****** GET JOB STATE/COLLATION STATUS BY ID *********
532
532
  async getStatus(jobId) {
@@ -4,7 +4,7 @@ import { QuorumService } from '../quorum';
4
4
  import { WorkerService } from '../worker';
5
5
  import { JobState, JobData, JobOutput, JobStatus, JobInterruptOptions } from '../../types/job';
6
6
  import { HotMeshConfig, HotMeshManifest } from '../../types/hotmesh';
7
- import { JobMessageCallback } from '../../types/quorum';
7
+ import { JobMessageCallback, QuorumProfile } from '../../types/quorum';
8
8
  import { JobStatsInput, GetStatsOptions, IdsResponse, StatsResponse } from '../../types/stats';
9
9
  import { StreamCode, StreamData, StreamDataResponse, StreamStatus } from '../../types/stream';
10
10
  import { StringAnyType } from '../../types/serializer';
@@ -31,10 +31,10 @@ declare class HotMeshService {
31
31
  punsub(wild: string): Promise<void>;
32
32
  pubsub(topic: string, data?: JobData, context?: JobState | null, timeout?: number): Promise<JobOutput>;
33
33
  add(streamData: StreamData | StreamDataResponse): Promise<string>;
34
+ rollCall(delay?: number): Promise<QuorumProfile[]>;
34
35
  plan(path: string): Promise<HotMeshManifest>;
35
36
  deploy(pathOrYAML: string): Promise<HotMeshManifest>;
36
37
  activate(version: string, delay?: number): Promise<boolean>;
37
- inventory(version: string, delay?: number): Promise<number>;
38
38
  getStats(topic: string, query: JobStatsInput): Promise<StatsResponse>;
39
39
  getStatus(jobId: string): Promise<JobStatus>;
40
40
  getState(topic: string, jobId: string): Promise<JobOutput>;
@@ -90,6 +90,9 @@ class HotMeshService {
90
90
  return await this.engine.add(streamData);
91
91
  }
92
92
  // ************* COMPILER METHODS *************
93
+ async rollCall(delay) {
94
+ return await this.quorum?.rollCall(delay);
95
+ }
93
96
  async plan(path) {
94
97
  return await this.engine?.plan(path);
95
98
  }
@@ -100,10 +103,6 @@ class HotMeshService {
100
103
  //activation is a quorum operation
101
104
  return await this.quorum?.activate(version, delay);
102
105
  }
103
- async inventory(version, delay) {
104
- //get count of all peers
105
- return await this.quorum?.inventory(delay);
106
- }
107
106
  // ************* REPORTER METHODS *************
108
107
  async getStats(topic, query) {
109
108
  return await this.engine?.getStats(topic, query);
@@ -3,15 +3,15 @@ import { ILogger } from '../logger';
3
3
  import { StoreService } from '../store';
4
4
  import { SubService } from '../sub';
5
5
  import { CacheMode } from '../../types/cache';
6
- import { QuorumMessageCallback, SubscriptionCallback, ThrottleMessage } from '../../types/quorum';
7
- import { HotMeshApps, HotMeshConfig } from '../../types/hotmesh';
6
+ import { QuorumMessageCallback, QuorumProfile, SubscriptionCallback, ThrottleMessage } from '../../types/quorum';
7
+ import { HotMeshConfig } from '../../types/hotmesh';
8
8
  import { RedisClient, RedisMulti } from '../../types/redis';
9
9
  declare class QuorumService {
10
10
  namespace: string;
11
- apps: HotMeshApps | null;
12
11
  appId: string;
13
12
  guid: string;
14
13
  engine: EngineService;
14
+ profiles: QuorumProfile[];
15
15
  store: StoreService<RedisClient, RedisMulti> | null;
16
16
  subscribe: SubService<RedisClient, RedisMulti> | null;
17
17
  logger: ILogger;
@@ -24,12 +24,12 @@ declare class QuorumService {
24
24
  initStoreChannel(store: RedisClient): Promise<void>;
25
25
  initSubChannel(sub: RedisClient): Promise<void>;
26
26
  subscriptionHandler(): SubscriptionCallback;
27
- sayPong(appId: string, guid: string, originator: string): Promise<void>;
28
- requestQuorum(delay?: number): Promise<number>;
27
+ sayPong(appId: string, guid: string, originator: string, details?: boolean): Promise<void>;
28
+ requestQuorum(delay?: number, details?: boolean): Promise<number>;
29
29
  pub(quorumMessage: ThrottleMessage): Promise<boolean>;
30
30
  sub(callback: QuorumMessageCallback): Promise<void>;
31
31
  unsub(callback: QuorumMessageCallback): Promise<void>;
32
- inventory(delay?: number): Promise<number>;
32
+ rollCall(delay?: number): Promise<QuorumProfile[]>;
33
33
  activate(version: string, delay?: number): Promise<boolean>;
34
34
  }
35
35
  export { QuorumService };
@@ -12,6 +12,7 @@ const redis_2 = require("../sub/clients/redis");
12
12
  const QUORUM_DELAY = 250;
13
13
  class QuorumService {
14
14
  constructor() {
15
+ this.profiles = [];
15
16
  this.cacheMode = 'cache';
16
17
  this.untilVersion = null;
17
18
  this.quorum = null;
@@ -68,10 +69,13 @@ class QuorumService {
68
69
  self.engine.setCacheMode(message.cache_mode, message.until_version);
69
70
  }
70
71
  else if (message.type === 'ping') {
71
- this.sayPong(self.appId, self.guid, message.originator);
72
+ self.sayPong(self.appId, self.guid, message.originator, message.details);
72
73
  }
73
74
  else if (message.type === 'pong' && self.guid === message.originator) {
74
75
  self.quorum = self.quorum + 1;
76
+ if (message.profile) {
77
+ self.profiles.push(message.profile);
78
+ }
75
79
  }
76
80
  else if (message.type === 'throttle') {
77
81
  self.engine.throttle(message.throttle);
@@ -91,13 +95,31 @@ class QuorumService {
91
95
  }
92
96
  };
93
97
  }
94
- async sayPong(appId, guid, originator) {
95
- this.store.publish(key_1.KeyType.QUORUM, { type: 'pong', guid, originator }, appId);
98
+ async sayPong(appId, guid, originator, details = false) {
99
+ let profile;
100
+ if (details) {
101
+ profile = {
102
+ engine_id: this.guid,
103
+ namespace: this.namespace,
104
+ app_id: this.appId,
105
+ stream: this.engine.stream.mintKey(key_1.KeyType.STREAMS, { appId: this.appId })
106
+ };
107
+ }
108
+ this.store.publish(key_1.KeyType.QUORUM, {
109
+ type: 'pong',
110
+ guid, originator,
111
+ profile,
112
+ }, appId);
96
113
  }
97
- async requestQuorum(delay = QUORUM_DELAY) {
114
+ async requestQuorum(delay = QUORUM_DELAY, details = false) {
98
115
  const quorum = this.quorum;
99
116
  this.quorum = 0;
100
- await this.store.publish(key_1.KeyType.QUORUM, { type: 'ping', originator: this.guid }, this.appId);
117
+ this.profiles.length = 0;
118
+ await this.store.publish(key_1.KeyType.QUORUM, {
119
+ type: 'ping',
120
+ originator: this.guid,
121
+ details,
122
+ }, this.appId);
101
123
  await (0, utils_1.sleepFor)(delay);
102
124
  return quorum;
103
125
  }
@@ -117,12 +139,26 @@ class QuorumService {
117
139
  this.callbacks = this.callbacks.filter(cb => cb !== callback);
118
140
  }
119
141
  // ************* COMPILER METHODS *************
120
- async inventory(delay = QUORUM_DELAY) {
121
- await this.requestQuorum(delay);
122
- const q1 = await this.requestQuorum(delay);
123
- const q2 = await this.requestQuorum(delay);
124
- const q3 = await this.requestQuorum(delay);
125
- return Math.round((q1 + q2 + q3) / 3);
142
+ async rollCall(delay = QUORUM_DELAY) {
143
+ await this.requestQuorum(delay, true);
144
+ const targetStreams = [];
145
+ const multi = this.store.getMulti();
146
+ this.profiles.forEach((profile) => {
147
+ if (!targetStreams.includes(profile.stream)) {
148
+ targetStreams.push(profile.stream);
149
+ this.store.xlen(profile.stream, multi);
150
+ }
151
+ });
152
+ const stream_depths = await multi.exec();
153
+ this.profiles.forEach(async (profile) => {
154
+ const index = targetStreams.indexOf(profile.stream);
155
+ if (index != -1) {
156
+ profile.stream_depth = Array.isArray(stream_depths[index]) ?
157
+ stream_depths[index][1] :
158
+ stream_depths[index];
159
+ }
160
+ });
161
+ return this.profiles;
126
162
  }
127
163
  async activate(version, delay = QUORUM_DELAY) {
128
164
  version = version.toString();
@@ -17,8 +17,8 @@ class Router {
17
17
  this.topic = config.topic;
18
18
  this.stream = stream;
19
19
  this.store = store;
20
- this.reclaimDelay = config.reclaimDelay || enums_1.XCLAIM_DELAY_MS;
21
- this.reclaimCount = config.reclaimCount || enums_1.XCLAIM_COUNT;
20
+ this.reclaimDelay = config.reclaimDelay || enums_1.HMSH_XCLAIM_DELAY_MS;
21
+ this.reclaimCount = config.reclaimCount || enums_1.HMSH_XCLAIM_COUNT;
22
22
  this.logger = logger;
23
23
  }
24
24
  async createGroup(stream, group) {
@@ -48,7 +48,9 @@ class Router {
48
48
  return;
49
49
  }
50
50
  try {
51
- const result = await this.stream.xreadgroup('GROUP', group, consumer, 'BLOCK', enums_1.BLOCK_TIME_MS, 'STREAMS', stream, '>');
51
+ //randomizer that asymptotes at 150% of `HMSH_BLOCK_TIME_MS`
52
+ const streamDuration = enums_1.HMSH_BLOCK_TIME_MS + Math.round((enums_1.HMSH_BLOCK_TIME_MS * Math.random()));
53
+ const result = await this.stream.xreadgroup('GROUP', group, consumer, 'BLOCK', streamDuration, 'STREAMS', stream, '>');
52
54
  if (this.isStreamMessage(result)) {
53
55
  const [[, messages]] = result;
54
56
  for (const [id, message] of messages) {
@@ -70,7 +72,7 @@ class Router {
70
72
  if (this.shouldConsume && process.env.NODE_ENV !== 'test') {
71
73
  this.logger.error(`stream-consume-message-error`, { err, stream, group, consumer });
72
74
  this.errorCount++;
73
- const timeout = Math.min(enums_1.GRADUATED_INTERVAL_MS * (2 ** this.errorCount), enums_1.MAX_TIMEOUT_MS);
75
+ const timeout = Math.min(enums_1.HMSH_GRADUATED_INTERVAL_MS * (2 ** this.errorCount), enums_1.HMSH_MAX_TIMEOUT_MS);
74
76
  setTimeout(consume.bind(this), timeout);
75
77
  }
76
78
  }
@@ -90,7 +92,7 @@ class Router {
90
92
  telemetry.startStreamSpan(input, this.role);
91
93
  output = await this.execStreamLeg(input, stream, id, callback.bind(this));
92
94
  if (output?.status === stream_1.StreamStatus.ERROR) {
93
- telemetry.setStreamError(`Function Status Code ${output.code || enums_1.STATUS_CODE_UNKNOWN}`);
95
+ telemetry.setStreamError(`Function Status Code ${output.code || enums_1.HMSH_CODE_UNKNOWN}`);
94
96
  }
95
97
  this.errorCount = 0;
96
98
  }
@@ -153,7 +155,7 @@ class Router {
153
155
  const errorCode = output.code.toString();
154
156
  const policy = policies?.[errorCode];
155
157
  const maxRetries = policy?.[0];
156
- const tryCount = Math.min(input.metadata.try || 0, enums_1.MAX_RETRIES);
158
+ const tryCount = Math.min(input.metadata.try || 0, enums_1.HMSH_MAX_RETRIES);
157
159
  //only possible values for maxRetries are 1, 2, 3
158
160
  //only possible values for tryCount are 0, 1, 2
159
161
  if (maxRetries > tryCount) {
@@ -168,7 +170,7 @@ class Router {
168
170
  error.message = err.message;
169
171
  }
170
172
  else {
171
- error.message = enums_1.STATUS_MESSAGE_UNKNOWN;
173
+ error.message = enums_1.HMSH_STATUS_UNKNOWN;
172
174
  }
173
175
  if (typeof err.stack === 'string') {
174
176
  error.stack = err.stack;
@@ -178,14 +180,14 @@ class Router {
178
180
  }
179
181
  return {
180
182
  status: 'error',
181
- code: enums_1.STATUS_CODE_UNKNOWN,
183
+ code: enums_1.HMSH_CODE_UNKNOWN,
182
184
  metadata: { ...input.metadata, guid: (0, utils_1.guid)() },
183
185
  data: error
184
186
  };
185
187
  }
186
188
  structureUnacknowledgedError(input) {
187
189
  const message = 'stream message max delivery count exceeded';
188
- const code = enums_1.STATUS_CODE_UNACKED;
190
+ const code = enums_1.HMSH_CODE_UNACKED;
189
191
  const data = { message, code };
190
192
  const output = {
191
193
  metadata: { ...input.metadata, guid: (0, utils_1.guid)() },
@@ -198,9 +200,9 @@ class Router {
198
200
  return output;
199
201
  }
200
202
  structureError(input, output) {
201
- const message = output.data?.message ? output.data?.message.toString() : enums_1.STATUS_MESSAGE_UNKNOWN;
203
+ const message = output.data?.message ? output.data?.message.toString() : enums_1.HMSH_STATUS_UNKNOWN;
202
204
  const statusCode = output.code || output.data?.code;
203
- const code = isNaN(statusCode) ? enums_1.STATUS_CODE_UNKNOWN : parseInt(statusCode.toString());
205
+ const code = isNaN(statusCode) ? enums_1.HMSH_CODE_UNKNOWN : parseInt(statusCode.toString());
204
206
  const data = { message, code };
205
207
  if (typeof output.data?.error === 'object') {
206
208
  data.error = { ...output.data.error };
@@ -216,7 +218,7 @@ class Router {
216
218
  for (const instance of [...Router.instances]) {
217
219
  instance.stopConsuming();
218
220
  }
219
- await (0, utils_1.sleepFor)(enums_1.BLOCK_TIME_MS);
221
+ await (0, utils_1.sleepFor)(enums_1.HMSH_BLOCK_TIME_MS * 2);
220
222
  }
221
223
  async stopConsuming() {
222
224
  this.shouldConsume = false;
@@ -236,7 +238,7 @@ class Router {
236
238
  this.throttle = delayInMillis;
237
239
  this.logger.info(`stream-throttle-reset`, { delay: this.throttle, topic: this.topic });
238
240
  }
239
- async claimUnacknowledged(stream, group, consumer, idleTimeMs = this.reclaimDelay, limit = enums_1.XPENDING_COUNT) {
241
+ async claimUnacknowledged(stream, group, consumer, idleTimeMs = this.reclaimDelay, limit = enums_1.HMSH_XPENDING_COUNT) {
240
242
  let pendingMessages = [];
241
243
  const pendingMessagesInfo = await this.stream.xpending(stream, group, '-', '+', limit); //[[ '1688768134881-0', 'testConsumer1', 1017, 1 ]]
242
244
  for (const pendingMessageInfo of pendingMessagesInfo) {
@@ -265,7 +267,7 @@ class Router {
265
267
  // ii) corrupt hardware/network/transport/etc
266
268
  // 3b) system error: Redis unable to accept `xadd` request
267
269
  // 4c) system error: Redis unable to accept `xdel`/`xack` request
268
- this.logger.error('stream-message-max-delivery-count-exceeded', { id, stream, group, consumer, code: enums_1.STATUS_CODE_UNACKED, count });
270
+ this.logger.error('stream-message-max-delivery-count-exceeded', { id, stream, group, consumer, code: enums_1.HMSH_CODE_UNACKED, count });
269
271
  const streamData = reclaimedMessage[0]?.[1]?.[1];
270
272
  //fatal risk point 1 of 3): json is corrupt
271
273
  const [err, input] = this.parseStreamData(streamData);
@@ -24,5 +24,6 @@ declare class IORedisStoreService extends StoreService<RedisClientType, RedisMul
24
24
  xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
25
25
  xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
26
26
  xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
27
+ xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
27
28
  }
28
29
  export { IORedisStoreService };
@@ -105,5 +105,14 @@ class IORedisStoreService extends index_1.StoreService {
105
105
  throw error;
106
106
  }
107
107
  }
108
+ async xlen(key, multi) {
109
+ try {
110
+ return await (multi || this.redisClient).xlen(key);
111
+ }
112
+ catch (error) {
113
+ this.logger.error(`Error getting stream depth: ${key}`, { error });
114
+ throw error;
115
+ }
116
+ }
108
117
  }
109
118
  exports.IORedisStoreService = IORedisStoreService;
@@ -26,5 +26,6 @@ declare class RedisStoreService extends StoreService<RedisClientType, RedisMulti
26
26
  xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
27
27
  xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
28
28
  xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
29
+ xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
29
30
  }
30
31
  export { RedisStoreService };
@@ -30,6 +30,7 @@ class RedisStoreService extends index_1.StoreService {
30
30
  rpush: 'RPUSH',
31
31
  xack: 'XACK',
32
32
  xdel: 'XDEL',
33
+ xlen: 'XLEN',
33
34
  };
34
35
  }
35
36
  getMulti() {
@@ -141,5 +142,20 @@ class RedisStoreService extends index_1.StoreService {
141
142
  throw error;
142
143
  }
143
144
  }
145
+ async xlen(key, multi) {
146
+ try {
147
+ if (multi) {
148
+ multi.XLEN(key);
149
+ return multi;
150
+ }
151
+ else {
152
+ return await this.redisClient.XLEN(key);
153
+ }
154
+ }
155
+ catch (error) {
156
+ this.logger.error(`Error getting stream depth: ${key}`, { error });
157
+ throw error;
158
+ }
159
+ }
144
160
  }
145
161
  exports.RedisStoreService = RedisStoreService;
@@ -11,6 +11,7 @@ import { IdsData, JobStatsRange, StatsType } from '../../types/stats';
11
11
  import { Transitions } from '../../types/transition';
12
12
  import { ReclaimedMessageType } from '../../types/stream';
13
13
  import { JobCompletionOptions, JobInterruptOptions } from '../../types/job';
14
+ import { WorkListTaskType } from '../../types/task';
14
15
  interface AbstractRedisClient {
15
16
  exec(): any;
16
17
  }
@@ -31,6 +32,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
31
32
  abstract xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
32
33
  abstract xack(key: string, group: string, id: string, multi?: U): Promise<number | U>;
33
34
  abstract xdel(key: string, id: string, multi?: U): Promise<number | U>;
35
+ abstract xlen(key: string, multi?: U): Promise<number | U>;
34
36
  constructor(redisClient: T);
35
37
  init(namespace: string, appId: string, logger: ILogger): Promise<HotMeshApps>;
36
38
  isSuccessful(result: any): boolean;
@@ -61,12 +63,16 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
61
63
  activateAppVersion(id: string, version: string): Promise<boolean>;
62
64
  registerAppVersion(appId: string, version: string): Promise<any>;
63
65
  /**
64
- * Registers jobId with the originJobId that spawned it. In the future,
65
- * when originJobId is interrupted or expired, the items in the
66
- * list (added via RPUSH) are LPOPed. If origin was expired, then
67
- * LPOPed items from the list are likewise expired;
66
+ * Registers the job, `jobId`, with `originJobId`. In the future,
67
+ * when `originJobId` is interrupted/expired, the items in the
68
+ * list (added via RPUSH) will be interrupted/expired (removed via LPOPed).
68
69
  */
69
- setDependency(originJobId: string, topic: string, jobId: string, gId: string, multi?: U): Promise<any>;
70
+ registerJobDependency(originJobId: string, topic: string, jobId: string, gId: string, multi?: U): Promise<any>;
71
+ /**
72
+ * Ensures a `hook signal` is delisted when its parent activity/job
73
+ * is interrupted/expired.
74
+ */
75
+ registerSignalDependency(jobId: string, signalKey: string, multi?: U): Promise<any>;
70
76
  setStats(jobKey: string, jobId: string, dateTime: string, stats: StatsType, appVersion: AppVID, multi?: U): Promise<any>;
71
77
  hGetAllResult(result: any): any;
72
78
  getJobStats(jobKeys: string[]): Promise<JobStatsRange>;
@@ -105,15 +111,15 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
105
111
  * expired at a future date; options indicate whether this
106
112
  * is a standard `expire` or an `interrupt`
107
113
  */
108
- registerExpireJob(jobId: string, deletionTime: number, options: JobCompletionOptions): Promise<void>;
114
+ registerDependenciesForCleanup(jobId: string, deletionTime: number, options: JobCompletionOptions): Promise<void>;
109
115
  /**
110
116
  * registers a hook activity to be awakened (uses ZSET to
111
117
  * store the 'sleep group' and LIST to store the events
112
118
  * for the given sleep group. Sleep groups are
113
119
  * organized into 'n'-second blocks (LISTS))
114
120
  */
115
- registerTimeHook(jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt', deletionTime: number, multi?: U): Promise<void>;
116
- getNextTimeJob(listKey?: string): Promise<[listKey: string, jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt'] | boolean>;
121
+ registerTimeHook(jobId: string, gId: string, activityId: string, type: WorkListTaskType, deletionTime: number, multi?: U): Promise<void>;
122
+ getNextTask(listKey?: string): Promise<[listKey: string, jobId: string, gId: string, activityId: string, type: WorkListTaskType] | boolean>;
117
123
  /**
118
124
  * when processing time jobs, the target LIST ID returned
119
125
  * from the ZSET query can be prefixed to denote what to
@@ -122,7 +128,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
122
128
  * generic LIST (lists typically contain target job ids)
123
129
  * @param {string} listKey - for example `::INTERRUPT::job123` or `job123`
124
130
  */
125
- resolveKeyContext(listKey: string): [('sleep' | 'expire' | 'interrupt'), string];
131
+ resolveTaskKeyContext(listKey: string): [('sleep' | 'expire' | 'interrupt' | 'delist'), string];
126
132
  /**
127
133
  * Interrupts a job and sets sets a job error (410), if 'throw'!=false.
128
134
  * This method is called by the engine and not by an activity and is
@@ -104,7 +104,7 @@ class StoreService {
104
104
  * check for and process work items in the
105
105
  * time and signal task queues.
106
106
  */
107
- async reserveScoutRole(scoutType, delay = enums_1.SCOUT_INTERVAL_SECONDS) {
107
+ async reserveScoutRole(scoutType, delay = enums_1.HMSH_SCOUT_INTERVAL_SECONDS) {
108
108
  const key = this.mintKey(key_1.KeyType.WORK_ITEMS, { appId: this.appId, scoutType });
109
109
  const success = await this.redisClient[this.commands.setnx](key, `${scoutType}:${(0, utils_1.formatISODate)(new Date())}`);
110
110
  if (this.isSuccessful(success)) {
@@ -308,16 +308,35 @@ class StoreService {
308
308
  return await this.redisClient[this.commands.hset](key, payload);
309
309
  }
310
310
  /**
311
- * Registers jobId with the originJobId that spawned it. In the future,
312
- * when originJobId is interrupted or expired, the items in the
313
- * list (added via RPUSH) are LPOPed. If origin was expired, then
314
- * LPOPed items from the list are likewise expired;
311
+ * Registers the job, `jobId`, with `originJobId`. In the future,
312
+ * when `originJobId` is interrupted/expired, the items in the
313
+ * list (added via RPUSH) will be interrupted/expired (removed via LPOPed).
315
314
  */
316
- async setDependency(originJobId, topic, jobId, gId, multi) {
315
+ async registerJobDependency(originJobId, topic, jobId, gId, multi) {
317
316
  const privateMulti = multi || this.getMulti();
318
- const depParams = { appId: this.appId, jobId: originJobId };
319
- const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, depParams);
320
- privateMulti[this.commands.rpush](depKey, `expire::${topic}::${gId}::${jobId}`);
317
+ const dependencyParams = {
318
+ appId: this.appId,
319
+ jobId: originJobId,
320
+ };
321
+ const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, dependencyParams);
322
+ //tasks have '4' segments
323
+ const expireTask = `expire::${topic}::${gId}::${jobId}`;
324
+ privateMulti[this.commands.rpush](depKey, expireTask);
325
+ if (!multi) {
326
+ return await privateMulti.exec();
327
+ }
328
+ }
329
+ /**
330
+ * Ensures a `hook signal` is delisted when its parent activity/job
331
+ * is interrupted/expired.
332
+ */
333
+ async registerSignalDependency(jobId, signalKey, multi) {
334
+ const privateMulti = multi || this.getMulti();
335
+ const dependencyParams = { appId: this.appId, jobId };
336
+ const dependencyKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, dependencyParams);
337
+ //tasks have '4' segments
338
+ const delistTask = `delist::signal::${jobId}::${signalKey}`;
339
+ privateMulti[this.commands.rpush](dependencyKey, delistTask);
321
340
  if (!multi) {
322
341
  return await privateMulti.exec();
323
342
  }
@@ -612,11 +631,11 @@ class StoreService {
612
631
  }
613
632
  async setHookSignal(hook, multi) {
614
633
  const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
615
- const { topic, resolved, jobId } = hook;
616
- const payload = {
617
- [`${topic}:${resolved}`]: jobId
618
- };
619
- return await (multi || this.redisClient)[this.commands.hset](key, payload);
634
+ const { topic, resolved, jobId } = hook; //`${activityId}::${dad}::${gId}::${jobId}`
635
+ const signalKey = `${topic}:${resolved}`;
636
+ const payload = { [signalKey]: jobId };
637
+ await (multi || this.redisClient)[this.commands.hset](key, payload);
638
+ return await this.registerSignalDependency(jobId.split('::')[3], signalKey, multi);
620
639
  }
621
640
  async getHookSignal(topic, resolved) {
622
641
  const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
@@ -677,7 +696,7 @@ class StoreService {
677
696
  * expired at a future date; options indicate whether this
678
697
  * is a standard `expire` or an `interrupt`
679
698
  */
680
- async registerExpireJob(jobId, deletionTime, options) {
699
+ async registerDependenciesForCleanup(jobId, deletionTime, options) {
681
700
  const depParams = { appId: this.appId, jobId };
682
701
  const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, depParams);
683
702
  const context = options.interrupt ? 'INTERRUPT' : 'EXPIRE';
@@ -700,21 +719,25 @@ class StoreService {
700
719
  await this.zAdd(zsetKey, deletionTime.toString(), listKey, multi);
701
720
  }
702
721
  }
703
- async getNextTimeJob(listKey) {
704
- const existing = Boolean(listKey);
722
+ async getNextTask(listKey) {
705
723
  const zsetKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId });
706
724
  listKey = listKey || await this.zRangeByScore(zsetKey, 0, Date.now());
707
725
  if (listKey) {
708
- const [pType, pKey] = this.resolveKeyContext(listKey);
726
+ let [pType, pKey] = this.resolveTaskKeyContext(listKey);
709
727
  const timeEvent = await this.redisClient[this.commands.lpop](pKey);
710
728
  if (timeEvent) {
711
- //there are 3 time-related event triggers: sleep, expire, interrupt
712
- const [_type, activityId, gId, ...jobId] = timeEvent.split('::');
729
+ //there are 4 time-related task
730
+ //1) sleep (awaken), 2) expire, 3) interrupt, 4) delist
731
+ const [type, activityId, gId, ...jobId] = timeEvent.split('::');
732
+ if (type === 'delist') {
733
+ pType = 'delist';
734
+ }
713
735
  return [listKey, jobId.join('::'), gId, activityId, pType];
714
736
  }
715
737
  await this.redisClient[this.commands.zrem](zsetKey, listKey);
738
+ return true;
716
739
  }
717
- return existing;
740
+ return false;
718
741
  }
719
742
  /**
720
743
  * when processing time jobs, the target LIST ID returned
@@ -724,7 +747,7 @@ class StoreService {
724
747
  * generic LIST (lists typically contain target job ids)
725
748
  * @param {string} listKey - for example `::INTERRUPT::job123` or `job123`
726
749
  */
727
- resolveKeyContext(listKey) {
750
+ resolveTaskKeyContext(listKey) {
728
751
  if (listKey.startsWith('::INTERRUPT')) {
729
752
  return ['interrupt', listKey.split('::')[2]];
730
753
  }
@@ -766,7 +789,7 @@ class StoreService {
766
789
  this.serializer.resetSymbols(symKeys, symVals, {});
767
790
  //persists the standard 410 error (job is `gone`)
768
791
  const err = JSON.stringify({
769
- code: enums_1.STATUS_CODE_INTERRUPT,
792
+ code: enums_1.HMSH_CODE_INTERRUPT,
770
793
  message: options.reason ?? `job [${jobId}] interrupted`,
771
794
  job_id: jobId
772
795
  });
@@ -19,5 +19,6 @@ declare class IORedisStreamService extends StreamService<RedisClientType, RedisM
19
19
  xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
20
20
  xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
21
21
  xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
22
+ xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
22
23
  }
23
24
  export { IORedisStreamService };