@hotmeshio/hotmesh 0.0.38 → 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 (51) hide show
  1. package/README.md +13 -7
  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/router/index.js +16 -14
  20. package/build/services/store/index.d.ts +14 -9
  21. package/build/services/store/index.js +46 -23
  22. package/build/services/task/index.d.ts +10 -3
  23. package/build/services/task/index.js +35 -17
  24. package/build/types/durable.d.ts +3 -2
  25. package/build/types/hotmesh.d.ts +43 -2
  26. package/build/types/hotmesh.js +28 -0
  27. package/build/types/index.d.ts +3 -2
  28. package/build/types/index.js +3 -1
  29. package/build/types/logger.d.ts +1 -0
  30. package/build/types/logger.js +1 -0
  31. package/build/types/task.d.ts +1 -0
  32. package/build/types/task.js +2 -0
  33. package/modules/enums.ts +49 -35
  34. package/modules/errors.ts +17 -8
  35. package/modules/key.ts +3 -40
  36. package/package.json +1 -1
  37. package/services/activities/activity.ts +2 -2
  38. package/services/activities/hook.ts +18 -9
  39. package/services/activities/trigger.ts +10 -6
  40. package/services/durable/client.ts +31 -15
  41. package/services/durable/handle.ts +3 -3
  42. package/services/durable/worker.ts +4 -3
  43. package/services/engine/index.ts +13 -12
  44. package/services/router/index.ts +26 -24
  45. package/services/store/index.ts +59 -25
  46. package/services/task/index.ts +66 -24
  47. package/types/durable.ts +6 -5
  48. package/types/hotmesh.ts +47 -2
  49. package/types/index.ts +8 -1
  50. package/types/logger.ts +3 -1
  51. 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) {
@@ -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);
@@ -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
  }
@@ -62,12 +63,16 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
62
63
  activateAppVersion(id: string, version: string): Promise<boolean>;
63
64
  registerAppVersion(appId: string, version: string): Promise<any>;
64
65
  /**
65
- * Registers jobId with the originJobId that spawned it. In the future,
66
- * when originJobId is interrupted or expired, the items in the
67
- * list (added via RPUSH) are LPOPed. If origin was expired, then
68
- * 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).
69
69
  */
70
- 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>;
71
76
  setStats(jobKey: string, jobId: string, dateTime: string, stats: StatsType, appVersion: AppVID, multi?: U): Promise<any>;
72
77
  hGetAllResult(result: any): any;
73
78
  getJobStats(jobKeys: string[]): Promise<JobStatsRange>;
@@ -106,15 +111,15 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
106
111
  * expired at a future date; options indicate whether this
107
112
  * is a standard `expire` or an `interrupt`
108
113
  */
109
- registerExpireJob(jobId: string, deletionTime: number, options: JobCompletionOptions): Promise<void>;
114
+ registerDependenciesForCleanup(jobId: string, deletionTime: number, options: JobCompletionOptions): Promise<void>;
110
115
  /**
111
116
  * registers a hook activity to be awakened (uses ZSET to
112
117
  * store the 'sleep group' and LIST to store the events
113
118
  * for the given sleep group. Sleep groups are
114
119
  * organized into 'n'-second blocks (LISTS))
115
120
  */
116
- registerTimeHook(jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt', deletionTime: number, multi?: U): Promise<void>;
117
- 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>;
118
123
  /**
119
124
  * when processing time jobs, the target LIST ID returned
120
125
  * from the ZSET query can be prefixed to denote what to
@@ -123,7 +128,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
123
128
  * generic LIST (lists typically contain target job ids)
124
129
  * @param {string} listKey - for example `::INTERRUPT::job123` or `job123`
125
130
  */
126
- resolveKeyContext(listKey: string): [('sleep' | 'expire' | 'interrupt'), string];
131
+ resolveTaskKeyContext(listKey: string): [('sleep' | 'expire' | 'interrupt' | 'delist'), string];
127
132
  /**
128
133
  * Interrupts a job and sets sets a job error (410), if 'throw'!=false.
129
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
  });
@@ -4,6 +4,7 @@ import { StoreService } from '../store';
4
4
  import { HookInterface, HookRule } from '../../types/hook';
5
5
  import { JobCompletionOptions, JobState } from '../../types/job';
6
6
  import { RedisClient, RedisMulti } from '../../types/redis';
7
+ import { WorkListTaskType } from '../../types/task';
7
8
  declare class TaskService {
8
9
  store: StoreService<RedisClient, RedisMulti>;
9
10
  logger: ILogger;
@@ -13,12 +14,18 @@ declare class TaskService {
13
14
  processWebHooks(hookEventCallback: HookInterface): Promise<void>;
14
15
  enqueueWorkItems(keys: string[]): Promise<void>;
15
16
  registerJobForCleanup(jobId: string, inSeconds: number, options: JobCompletionOptions): Promise<void>;
16
- registerTimeHook(jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt', inSeconds?: number, multi?: RedisMulti): Promise<void>;
17
+ registerTimeHook(jobId: string, gId: string, activityId: string, type: WorkListTaskType, inSeconds?: number, multi?: RedisMulti): Promise<void>;
17
18
  /**
18
- * Should this engine instance play the role of 'scout' for the quorum.
19
+ * Should this engine instance play the role of 'scout' on behalf
20
+ * of the entire quorum? The scout role is responsible for processing
21
+ * task lists on behalf of the collective.
19
22
  */
20
23
  shouldScout(): Promise<boolean>;
21
- processTimeHooks(timeEventCallback: (jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt') => Promise<void>, listKey?: string): Promise<void>;
24
+ /**
25
+ * Callback handler that takes an item from a work list and
26
+ * processes according to its type
27
+ */
28
+ processTimeHooks(timeEventCallback: (jobId: string, gId: string, activityId: string, type: WorkListTaskType) => Promise<void>, listKey?: string): Promise<void>;
22
29
  cancelCleanup(): void;
23
30
  getHookRule(topic: string): Promise<HookRule | undefined>;
24
31
  registerWebHook(topic: string, context: JobState, dad: string, multi?: RedisMulti): Promise<string>;
@@ -4,6 +4,7 @@ exports.TaskService = void 0;
4
4
  const enums_1 = require("../../modules/enums");
5
5
  const utils_1 = require("../../modules/utils");
6
6
  const pipe_1 = require("../pipe");
7
+ const hotmesh_1 = require("../../types/hotmesh");
7
8
  class TaskService {
8
9
  constructor(store, logger) {
9
10
  this.cleanupTimeout = null;
@@ -31,19 +32,25 @@ class TaskService {
31
32
  async enqueueWorkItems(keys) {
32
33
  await this.store.addTaskQueues(keys);
33
34
  }
34
- async registerJobForCleanup(jobId, inSeconds = enums_1.EXPIRE_DURATION, options) {
35
+ async registerJobForCleanup(jobId, inSeconds = enums_1.HMSH_EXPIRE_DURATION, options) {
35
36
  if (inSeconds > 0) {
36
37
  await this.store.expireJob(jobId, inSeconds);
37
- const expireTimeSlot = Math.floor((Date.now() + (inSeconds * 1000)) / (enums_1.FIDELITY_SECONDS * 1000)) * (enums_1.FIDELITY_SECONDS * 1000); //n second awaken groups
38
- await this.store.registerExpireJob(jobId, expireTimeSlot, options);
38
+ const fromNow = Date.now() + (inSeconds * 1000);
39
+ const fidelityMS = enums_1.HMSH_FIDELITY_SECONDS * 1000;
40
+ const timeSlot = Math.floor(fromNow / fidelityMS) * fidelityMS;
41
+ await this.store.registerDependenciesForCleanup(jobId, timeSlot, options);
39
42
  }
40
43
  }
41
- async registerTimeHook(jobId, gId, activityId, type, inSeconds = enums_1.FIDELITY_SECONDS, multi) {
42
- const awakenTimeSlot = Math.floor((Date.now() + (inSeconds * 1000)) / (enums_1.FIDELITY_SECONDS * 1000)) * (enums_1.FIDELITY_SECONDS * 1000); //n second awaken groups
44
+ async registerTimeHook(jobId, gId, activityId, type, inSeconds = enums_1.HMSH_FIDELITY_SECONDS, multi) {
45
+ const fromNow = Date.now() + (inSeconds * 1000);
46
+ const fidelityMS = enums_1.HMSH_FIDELITY_SECONDS * 1000;
47
+ const awakenTimeSlot = Math.floor(fromNow / fidelityMS) * fidelityMS;
43
48
  await this.store.registerTimeHook(jobId, gId, activityId, type, awakenTimeSlot, multi);
44
49
  }
45
50
  /**
46
- * Should this engine instance play the role of 'scout' for the quorum.
51
+ * Should this engine instance play the role of 'scout' on behalf
52
+ * of the entire quorum? The scout role is responsible for processing
53
+ * task lists on behalf of the collective.
47
54
  */
48
55
  async shouldScout() {
49
56
  const wasScout = this.isScout;
@@ -52,31 +59,42 @@ class TaskService {
52
59
  if (!wasScout) {
53
60
  setTimeout(() => {
54
61
  this.isScout = false;
55
- }, enums_1.SCOUT_INTERVAL_SECONDS * 1000);
62
+ }, enums_1.HMSH_SCOUT_INTERVAL_SECONDS * 1000);
56
63
  }
57
64
  return true;
58
65
  }
59
66
  return false;
60
67
  }
68
+ /**
69
+ * Callback handler that takes an item from a work list and
70
+ * processes according to its type
71
+ */
61
72
  async processTimeHooks(timeEventCallback, listKey) {
62
73
  if (await this.shouldScout()) {
63
74
  try {
64
- const timeJob = await this.store.getNextTimeJob(listKey);
65
- if (Array.isArray(timeJob)) {
66
- //a queue had a job; try again immediately
67
- const [listKey, jobId, gId, activityId, type] = timeJob;
68
- await timeEventCallback(jobId, gId, activityId, type);
75
+ const workListTask = await this.store.getNextTask(listKey);
76
+ if (Array.isArray(workListTask)) {
77
+ const [listKey, target, gId, activityId, type] = workListTask;
78
+ if (type === 'delist') {
79
+ //delist the signalKey (target)
80
+ const key = this.store.mintKey(hotmesh_1.KeyType.SIGNALS, { appId: this.store.appId });
81
+ await this.store.redisClient[this.store.commands.hdel](key, target);
82
+ }
83
+ else {
84
+ //awaken/expire/interrupt
85
+ await timeEventCallback(target, gId, activityId, type);
86
+ }
69
87
  await (0, utils_1.sleepFor)(0);
70
88
  this.processTimeHooks(timeEventCallback, listKey);
71
89
  }
72
- else if (timeJob) {
73
- //a queue was just emptied; try again immediately
90
+ else if (workListTask) {
91
+ //a worklist was just emptied; try again immediately
74
92
  await (0, utils_1.sleepFor)(0);
75
93
  this.processTimeHooks(timeEventCallback);
76
94
  }
77
95
  else {
78
- //all queues are empty; sleep before checking
79
- let sleep = (0, utils_1.XSleepFor)(enums_1.FIDELITY_SECONDS * 1000);
96
+ //no worklists exist; sleep before checking
97
+ let sleep = (0, utils_1.XSleepFor)(enums_1.HMSH_FIDELITY_SECONDS * 1000);
80
98
  this.cleanupTimeout = sleep.timerId;
81
99
  await sleep.promise;
82
100
  this.processTimeHooks(timeEventCallback);
@@ -89,7 +107,7 @@ class TaskService {
89
107
  }
90
108
  else {
91
109
  //didn't get the scout role; try again in 'one-ish' minutes
92
- let sleep = (0, utils_1.XSleepFor)(enums_1.SCOUT_INTERVAL_SECONDS * 1000 * 2 * Math.random());
110
+ let sleep = (0, utils_1.XSleepFor)(enums_1.HMSH_SCOUT_INTERVAL_SECONDS * 1000 * 2 * Math.random());
93
111
  this.cleanupTimeout = sleep.timerId;
94
112
  await sleep.promise;
95
113
  this.processTimeHooks(timeEventCallback);
@@ -1,3 +1,4 @@
1
+ import { LogLevel } from './logger';
1
2
  import { RedisClass, RedisOptions } from './redis';
2
3
  type WorkflowConfig = {
3
4
  backoffCoefficient?: number;
@@ -151,12 +152,12 @@ type MeshOSActivityOptions = {
151
152
  type MeshOSWorkerOptions = {
152
153
  taskQueue?: string;
153
154
  allowList?: Array<MeshOSOptions | string>;
154
- logLevel?: string;
155
+ logLevel?: LogLevel;
155
156
  maxSystemRetries?: number;
156
157
  backoffCoefficient?: number;
157
158
  };
158
159
  type WorkerOptions = {
159
- logLevel?: string;
160
+ logLevel?: LogLevel;
160
161
  maxSystemRetries?: number;
161
162
  backoffCoefficient?: number;
162
163
  };
@@ -3,6 +3,47 @@ import { HotMeshService } from '../services/hotmesh';
3
3
  import { HookRules } from './hook';
4
4
  import { RedisClass, RedisClient, RedisOptions } from './redis';
5
5
  import { StreamData, StreamDataResponse } from './stream';
6
+ import { LogLevel } from './logger';
7
+ /**
8
+ * the full set of entity types that are stored in the key/value store
9
+ */
10
+ declare enum KeyType {
11
+ APP = "APP",
12
+ ENGINE_ID = "ENGINE",
13
+ HOOKS = "HOOKS",
14
+ JOB_DEPENDENTS = "JOB_DEPENDENTS",
15
+ JOB_STATE = "JOB_STATE",
16
+ JOB_STATS_GENERAL = "JOB_STATS_GENERAL",
17
+ JOB_STATS_MEDIAN = "JOB_STATS_MEDIAN",
18
+ JOB_STATS_INDEX = "JOB_STATS_INDEX",
19
+ HOTMESH = "HOTMESH",
20
+ QUORUM = "QUORUM",
21
+ SCHEMAS = "SCHEMAS",
22
+ SIGNALS = "SIGNALS",
23
+ STREAMS = "STREAMS",
24
+ SUBSCRIPTIONS = "SUBSCRIPTIONS",
25
+ SUBSCRIPTION_PATTERNS = "SUBSCRIPTION_PATTERNS",
26
+ SYMKEYS = "SYMKEYS",
27
+ SYMVALS = "SYMVALS",
28
+ TIME_RANGE = "TIME_RANGE",
29
+ WORK_ITEMS = "WORK_ITEMS"
30
+ }
31
+ /**
32
+ * minting keys, requires one or more of the following parameters
33
+ */
34
+ type KeyStoreParams = {
35
+ appId?: string;
36
+ engineId?: string;
37
+ appVersion?: string;
38
+ jobId?: string;
39
+ activityId?: string;
40
+ jobKey?: string;
41
+ dateTime?: string;
42
+ facet?: string;
43
+ topic?: string;
44
+ timeValue?: number;
45
+ scoutType?: 'signal' | 'time';
46
+ };
6
47
  type HotMesh = typeof HotMeshService;
7
48
  type RedisConfig = {
8
49
  class: RedisClass;
@@ -34,7 +75,7 @@ type HotMeshConfig = {
34
75
  namespace?: string;
35
76
  name?: string;
36
77
  logger?: ILogger;
37
- logLevel?: 'silly' | 'debug' | 'info' | 'warn' | 'error' | 'silent';
78
+ logLevel?: LogLevel;
38
79
  engine?: HotMeshEngine;
39
80
  workers?: HotMeshWorker[];
40
81
  };
@@ -79,4 +120,4 @@ type HotMeshApps = {
79
120
  export { HotMesh, HotMeshEngine, RedisConfig, HotMeshWorker, HotMeshSettings, HotMeshApp, //a single app in the db
80
121
  HotMeshApps, //object array of all apps in the db
81
122
  HotMeshConfig, //customer config
82
- HotMeshManifest, HotMeshGraph };
123
+ HotMeshManifest, HotMeshGraph, KeyType, KeyStoreParams, };
@@ -1,2 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyType = void 0;
4
+ /**
5
+ * the full set of entity types that are stored in the key/value store
6
+ */
7
+ var KeyType;
8
+ (function (KeyType) {
9
+ KeyType["APP"] = "APP";
10
+ KeyType["ENGINE_ID"] = "ENGINE";
11
+ KeyType["HOOKS"] = "HOOKS";
12
+ KeyType["JOB_DEPENDENTS"] = "JOB_DEPENDENTS";
13
+ KeyType["JOB_STATE"] = "JOB_STATE";
14
+ KeyType["JOB_STATS_GENERAL"] = "JOB_STATS_GENERAL";
15
+ KeyType["JOB_STATS_MEDIAN"] = "JOB_STATS_MEDIAN";
16
+ KeyType["JOB_STATS_INDEX"] = "JOB_STATS_INDEX";
17
+ KeyType["HOTMESH"] = "HOTMESH";
18
+ KeyType["QUORUM"] = "QUORUM";
19
+ KeyType["SCHEMAS"] = "SCHEMAS";
20
+ KeyType["SIGNALS"] = "SIGNALS";
21
+ KeyType["STREAMS"] = "STREAMS";
22
+ KeyType["SUBSCRIPTIONS"] = "SUBSCRIPTIONS";
23
+ KeyType["SUBSCRIPTION_PATTERNS"] = "SUBSCRIPTION_PATTERNS";
24
+ KeyType["SYMKEYS"] = "SYMKEYS";
25
+ KeyType["SYMVALS"] = "SYMVALS";
26
+ KeyType["TIME_RANGE"] = "TIME_RANGE";
27
+ KeyType["WORK_ITEMS"] = "WORK_ITEMS";
28
+ })(KeyType || (KeyType = {}));
29
+ exports.KeyType = KeyType;
30
+ ;