@hotmeshio/hotmesh 0.0.57 → 0.0.59

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 (90) hide show
  1. package/README.md +1 -1
  2. package/build/modules/enums.js +10 -1
  3. package/build/modules/key.d.ts +38 -0
  4. package/build/modules/key.js +46 -4
  5. package/build/modules/utils.d.ts +9 -0
  6. package/build/modules/utils.js +19 -1
  7. package/build/package.json +2 -1
  8. package/build/services/activities/activity.d.ts +28 -0
  9. package/build/services/activities/activity.js +46 -1
  10. package/build/services/activities/await.js +4 -0
  11. package/build/services/activities/cycle.d.ts +7 -0
  12. package/build/services/activities/cycle.js +16 -1
  13. package/build/services/activities/hook.d.ts +6 -0
  14. package/build/services/activities/hook.js +12 -2
  15. package/build/services/activities/interrupt.js +8 -0
  16. package/build/services/activities/signal.d.ts +6 -0
  17. package/build/services/activities/signal.js +15 -0
  18. package/build/services/activities/trigger.d.ts +4 -0
  19. package/build/services/activities/trigger.js +7 -1
  20. package/build/services/activities/worker.js +4 -0
  21. package/build/services/collator/index.d.ts +70 -0
  22. package/build/services/collator/index.js +91 -1
  23. package/build/services/compiler/deployer.js +38 -6
  24. package/build/services/compiler/index.d.ts +15 -0
  25. package/build/services/compiler/index.js +20 -0
  26. package/build/services/compiler/validator.d.ts +3 -0
  27. package/build/services/compiler/validator.js +25 -0
  28. package/build/services/connector/clients/ioredis.js +2 -0
  29. package/build/services/connector/clients/redis.js +2 -0
  30. package/build/services/connector/index.js +2 -0
  31. package/build/services/durable/client.d.ts +20 -0
  32. package/build/services/durable/client.js +25 -0
  33. package/build/services/durable/exporter.d.ts +22 -0
  34. package/build/services/durable/exporter.js +30 -1
  35. package/build/services/durable/handle.d.ts +36 -0
  36. package/build/services/durable/handle.js +46 -0
  37. package/build/services/durable/index.d.ts +4 -0
  38. package/build/services/durable/index.js +4 -0
  39. package/build/services/durable/schemas/factory.d.ts +29 -0
  40. package/build/services/durable/schemas/factory.js +29 -0
  41. package/build/services/durable/search.d.ts +97 -0
  42. package/build/services/durable/search.js +99 -0
  43. package/build/services/durable/worker.js +35 -6
  44. package/build/services/durable/workflow.d.ts +118 -0
  45. package/build/services/durable/workflow.js +153 -6
  46. package/build/services/engine/index.d.ts +5 -0
  47. package/build/services/engine/index.js +43 -1
  48. package/build/services/exporter/index.d.ts +27 -0
  49. package/build/services/exporter/index.js +33 -0
  50. package/build/services/hotmesh/index.js +8 -0
  51. package/build/services/logger/index.js +2 -0
  52. package/build/services/mapper/index.d.ts +14 -0
  53. package/build/services/mapper/index.js +14 -0
  54. package/build/services/pipe/functions/date.d.ts +7 -0
  55. package/build/services/pipe/functions/date.js +7 -0
  56. package/build/services/pipe/functions/math.js +2 -0
  57. package/build/services/pipe/index.d.ts +16 -0
  58. package/build/services/pipe/index.js +45 -3
  59. package/build/services/quorum/index.d.ts +7 -0
  60. package/build/services/quorum/index.js +21 -0
  61. package/build/services/reporter/index.d.ts +5 -0
  62. package/build/services/reporter/index.js +9 -0
  63. package/build/services/router/index.d.ts +9 -0
  64. package/build/services/router/index.js +30 -2
  65. package/build/services/serializer/index.js +23 -6
  66. package/build/services/store/cache.d.ts +19 -0
  67. package/build/services/store/cache.js +19 -0
  68. package/build/services/store/clients/ioredis.js +1 -0
  69. package/build/services/store/index.d.ts +55 -0
  70. package/build/services/store/index.js +81 -5
  71. package/build/services/stream/clients/ioredis.js +4 -1
  72. package/build/services/task/index.d.ts +9 -0
  73. package/build/services/task/index.js +31 -0
  74. package/build/services/telemetry/index.d.ts +7 -0
  75. package/build/services/telemetry/index.js +13 -1
  76. package/build/services/worker/index.d.ts +4 -0
  77. package/build/services/worker/index.js +6 -2
  78. package/build/types/activity.d.ts +81 -0
  79. package/build/types/durable.d.ts +255 -0
  80. package/build/types/exporter.d.ts +13 -0
  81. package/build/types/hotmesh.d.ts +10 -1
  82. package/build/types/hotmesh.js +3 -0
  83. package/build/types/index.js +1 -1
  84. package/build/types/job.d.ts +85 -0
  85. package/build/types/pipe.d.ts +65 -0
  86. package/build/types/quorum.d.ts +14 -0
  87. package/build/types/redis.d.ts +6 -0
  88. package/build/types/stream.d.ts +58 -0
  89. package/build/types/stream.js +4 -0
  90. package/package.json +2 -1
@@ -26,9 +26,12 @@ class QuorumService {
26
26
  instance.guid = guid;
27
27
  instance.logger = logger;
28
28
  instance.engine = engine;
29
+ //note: `quorum` shares/re-uses the engine's `store`/`sub` Redis clients
29
30
  await instance.initStoreChannel(config.engine.store);
30
31
  await instance.initSubChannel(config.engine.sub);
32
+ //general quorum subscription
31
33
  await instance.subscribe.subscribe(hotmesh_1.KeyType.QUORUM, instance.subscriptionHandler(), appId);
34
+ //app-specific quorum subscription (used for pubsub one-time request/response)
32
35
  await instance.subscribe.subscribe(hotmesh_1.KeyType.QUORUM, instance.subscriptionHandler(), appId, instance.guid);
33
36
  instance.engine.processWebHooks();
34
37
  instance.engine.processTimeHooks();
@@ -90,6 +93,7 @@ class QuorumService {
90
93
  else if (message.type === 'rollcall') {
91
94
  self.doRollCall(message);
92
95
  }
96
+ //if there are any callbacks, call them
93
97
  if (self.callbacks.length > 0) {
94
98
  self.callbacks.forEach(cb => cb(topic, message));
95
99
  }
@@ -131,6 +135,10 @@ class QuorumService {
131
135
  await (0, utils_1.sleepFor)(delay);
132
136
  return quorum;
133
137
  }
138
+ /**
139
+ * A quorum-wide command to broadcaset system details.
140
+ *
141
+ */
134
142
  async doRollCall(message) {
135
143
  let iteration = 0;
136
144
  let max = !isNaN(message.max) ? message.max : enums_1.HMSH_QUORUM_ROLLCALL_CYCLES;
@@ -157,15 +165,22 @@ class QuorumService {
157
165
  stop() {
158
166
  this.cancelRollCall();
159
167
  }
168
+ // ************* PUB/SUB METHODS *************
169
+ //publish a message to the quorum
160
170
  async pub(quorumMessage) {
161
171
  return await this.store.publish(hotmesh_1.KeyType.QUORUM, quorumMessage, this.appId, quorumMessage.topic || quorumMessage.guid);
162
172
  }
173
+ //subscribe user to quorum messages
163
174
  async sub(callback) {
175
+ //the quorum is always subscribed to the `quorum` topic; just register the fn
164
176
  this.callbacks.push(callback);
165
177
  }
178
+ //unsubscribe user from quorum messages
166
179
  async unsub(callback) {
180
+ //the quorum is always subscribed to the `quorum` topic; just unregister the fn
167
181
  this.callbacks = this.callbacks.filter(cb => cb !== callback);
168
182
  }
183
+ // ************* COMPILER METHODS *************
169
184
  async rollCall(delay = enums_1.HMSH_QUORUM_DELAY_MS) {
170
185
  await this.requestQuorum(delay, true);
171
186
  const targetStreams = [];
@@ -187,10 +202,14 @@ class QuorumService {
187
202
  });
188
203
  return this.profiles;
189
204
  }
205
+ /**
206
+ * request a quorum; if successful activate the app version
207
+ */
190
208
  async activate(version, delay = enums_1.HMSH_QUORUM_DELAY_MS, count = 0) {
191
209
  version = version.toString();
192
210
  const canActivate = await this.store.reserveScoutRole('activate', Math.ceil(delay * 6 / 1000) + 1);
193
211
  if (!canActivate) {
212
+ //another engine is already activating the app version
194
213
  this.logger.debug('quorum-activation-awaiting', { version });
195
214
  await (0, utils_1.sleepFor)(delay * 6);
196
215
  const app = await this.store.getApp(this.appId, true);
@@ -206,6 +225,7 @@ class QuorumService {
206
225
  this.store.publish(hotmesh_1.KeyType.QUORUM, { type: 'activate', cache_mode: 'nocache', until_version: version }, this.appId);
207
226
  await new Promise(resolve => setTimeout(resolve, delay));
208
227
  await this.store.releaseScoutRole('activate');
228
+ //confirm we received the activation message
209
229
  if (this.engine.untilVersion === version) {
210
230
  this.logger.info('quorum-activation-succeeded', { version });
211
231
  const { id } = config;
@@ -221,6 +241,7 @@ class QuorumService {
221
241
  this.logger.warn('quorum-rollcall-error', { q1, q2, q3, count });
222
242
  this.store.releaseScoutRole('activate');
223
243
  if (count < enums_1.HMSH_ACTIVATION_MAX_RETRY) {
244
+ //increase the delay (give the quorum time to respond) and try again
224
245
  return await this.activate(version, delay * 2, count + 1);
225
246
  }
226
247
  throw new Error(`Quorum not reached. Version ${version} not activated.`);
@@ -28,6 +28,11 @@ declare class ReporterService {
28
28
  getTargetForTime(key: string): string;
29
29
  getWorkItems(options: GetStatsOptions, facets: string[]): Promise<string[]>;
30
30
  private buildWorkerLists;
31
+ /**
32
+ * called by `trigger` activity to generate the stats that should
33
+ * be saved to the database. doesn't actually save the stats, but
34
+ * just generates the info that should be saved
35
+ */
31
36
  resolveTriggerStatistics({ stats: statsConfig }: TriggerActivity, context: JobState): StatsType;
32
37
  isGeneralMetric(metric: string): boolean;
33
38
  isMedianMetric(metric: string): boolean;
@@ -27,9 +27,11 @@ class ReporterService {
27
27
  }
28
28
  generateDateTimeSets(granularity, range, end, start) {
29
29
  if (granularity === 'infinity') {
30
+ //if granularity is infinity, it means a date/time sequence/slice is not used to further segment the statistics
30
31
  return ['0'];
31
32
  }
32
33
  if (!range) {
34
+ //pluck just a single value when no range provided
33
35
  range = '0m';
34
36
  }
35
37
  const granularitiesInMinutes = {
@@ -47,6 +49,7 @@ class ReporterService {
47
49
  if (rangeMinutes === null) {
48
50
  throw new Error('Invalid range value.');
49
51
  }
52
+ // If start is provided, use it. Otherwise, calculate it from the end time and range.
50
53
  let startTime;
51
54
  let endTime;
52
55
  if (start) {
@@ -57,6 +60,7 @@ class ReporterService {
57
60
  endTime = end === 'NOW' ? new Date() : new Date(end);
58
61
  startTime = new Date(endTime.getTime() - rangeMinutes * 60 * 1000);
59
62
  }
63
+ // Round the start time to the nearest granularity unit
60
64
  startTime.setUTCMinutes(Math.floor(startTime.getUTCMinutes() / granularityMinutes) * granularityMinutes);
61
65
  const dateTimeSets = [];
62
66
  for (let time = startTime; time <= endTime; time.setUTCMinutes(time.getUTCMinutes() + granularityMinutes)) {
@@ -266,6 +270,11 @@ class ReporterService {
266
270
  }
267
271
  return workerLists;
268
272
  }
273
+ /**
274
+ * called by `trigger` activity to generate the stats that should
275
+ * be saved to the database. doesn't actually save the stats, but
276
+ * just generates the info that should be saved
277
+ */
269
278
  resolveTriggerStatistics({ stats: statsConfig }, context) {
270
279
  const stats = {
271
280
  general: [],
@@ -30,6 +30,15 @@ declare class Router {
30
30
  private resetThrottleState;
31
31
  createGroup(stream: string, group: string): Promise<void>;
32
32
  publishMessage(topic: string, streamData: StreamData | StreamDataResponse, multi?: RedisMulti): Promise<string | RedisMulti>;
33
+ /**
34
+ * An adjustable throttle that will interrupt a sleeping
35
+ * router if the throttle is reduced and the sleep time
36
+ * has elapsed. If the throttle is increased, or if
37
+ * the sleep time has not elapsed, the router will continue
38
+ * to sleep until the new termination point. This
39
+ * allows for dynamic, elastic throttling with smooth
40
+ * acceleration and deceleration.
41
+ */
33
42
  customSleep(): Promise<void>;
34
43
  consumeMessages(stream: string, group: string, consumer: string, callback: (streamData: StreamData) => Promise<StreamDataResponse | void>): Promise<void>;
35
44
  isStreamMessage(result: any): boolean;
@@ -47,13 +47,22 @@ class Router {
47
47
  const stream = this.store.mintKey(key_1.KeyType.STREAMS, { appId: this.store.appId, topic });
48
48
  return await this.store.xadd(stream, '*', 'message', JSON.stringify(streamData), multi);
49
49
  }
50
+ /**
51
+ * An adjustable throttle that will interrupt a sleeping
52
+ * router if the throttle is reduced and the sleep time
53
+ * has elapsed. If the throttle is increased, or if
54
+ * the sleep time has not elapsed, the router will continue
55
+ * to sleep until the new termination point. This
56
+ * allows for dynamic, elastic throttling with smooth
57
+ * acceleration and deceleration.
58
+ */
50
59
  async customSleep() {
51
60
  if (this.throttle === 0)
52
61
  return;
53
62
  if (this.isSleeping)
54
63
  return;
55
64
  this.isSleeping = true;
56
- let startTime = Date.now();
65
+ let startTime = Date.now(); //anchor the origin
57
66
  await new Promise(async (outerResolve) => {
58
67
  this.sleepPromiseResolve = outerResolve;
59
68
  let elapsedTime = Date.now() - startTime;
@@ -81,6 +90,7 @@ class Router {
81
90
  return;
82
91
  }
83
92
  try {
93
+ //randomizer that asymptotes at 150% of `HMSH_BLOCK_TIME_MS`
84
94
  const streamDuration = enums_1.HMSH_BLOCK_TIME_MS + Math.round((enums_1.HMSH_BLOCK_TIME_MS * Math.random()));
85
95
  const result = await this.stream.xreadgroup('GROUP', group, consumer, 'BLOCK', streamDuration, 'STREAMS', stream, '>');
86
96
  if (this.isStreamMessage(result)) {
@@ -89,6 +99,7 @@ class Router {
89
99
  await this.consumeOne(stream, group, id, message, callback);
90
100
  }
91
101
  }
102
+ // Check for pending messages (note: Redis 6.2 simplifies)
92
103
  const now = Date.now();
93
104
  if (now - lastCheckedPendingMessagesAt > this.reclaimDelay) {
94
105
  lastCheckedPendingMessagesAt = now;
@@ -162,6 +173,7 @@ class Router {
162
173
  await (0, utils_1.sleepFor)(timeout);
163
174
  return await this.publishMessage(input.metadata.topic, {
164
175
  data: input.data,
176
+ //note: retain guid (this is a retry attempt)
165
177
  metadata: { ...input.metadata, try: (input.metadata.try || 0) + 1 },
166
178
  policies: input.policies,
167
179
  });
@@ -181,12 +193,16 @@ class Router {
181
193
  }
182
194
  }
183
195
  shouldRetry(input, output) {
196
+ //const isUnhandledEngineError = output.code === 500;
184
197
  const policies = input.policies?.retry;
185
198
  const errorCode = output.code.toString();
186
199
  const policy = policies?.[errorCode];
187
200
  const maxRetries = policy?.[0];
188
201
  const tryCount = Math.min(input.metadata.try || 0, enums_1.HMSH_MAX_RETRIES);
202
+ //only possible values for maxRetries are 1, 2, 3
203
+ //only possible values for tryCount are 0, 1, 2
189
204
  if (maxRetries > tryCount) {
205
+ // 10ms, 100ms, or 1000ms delays between system retries
190
206
  return [true, Math.pow(10, tryCount + 1)];
191
207
  }
192
208
  return [false, 0];
@@ -222,6 +238,7 @@ class Router {
222
238
  code,
223
239
  data,
224
240
  };
241
+ //send unacknowleded errors to the engine (it has no topic)
225
242
  delete output.metadata.topic;
226
243
  return output;
227
244
  }
@@ -265,6 +282,7 @@ class Router {
265
282
  }
266
283
  const wasDecreased = delayInMillis < this.throttle;
267
284
  this.throttle = delayInMillis;
285
+ // If the throttle was decreased, and we're in the middle of a sleep cycle, adjust immediately
268
286
  if (wasDecreased) {
269
287
  if (this.sleepTimout) {
270
288
  clearTimeout(this.sleepTimout);
@@ -276,7 +294,7 @@ class Router {
276
294
  }
277
295
  async claimUnacknowledged(stream, group, consumer, idleTimeMs = this.reclaimDelay, limit = enums_1.HMSH_XPENDING_COUNT) {
278
296
  let pendingMessages = [];
279
- const pendingMessagesInfo = await this.stream.xpending(stream, group, '-', '+', limit);
297
+ const pendingMessagesInfo = await this.stream.xpending(stream, group, '-', '+', limit); //[[ '1688768134881-0', 'testConsumer1', 1017, 1 ]]
280
298
  for (const pendingMessageInfo of pendingMessagesInfo) {
281
299
  if (Array.isArray(pendingMessageInfo)) {
282
300
  const [id, , elapsedTimeMs, deliveryCount] = pendingMessageInfo;
@@ -296,8 +314,16 @@ class Router {
296
314
  return pendingMessages;
297
315
  }
298
316
  async expireUnacknowledged(reclaimedMessage, stream, group, consumer, id, count) {
317
+ //The stream activity was not processed within established limits. Possibilities Include:
318
+ // 1) user error: the workers were not properly configured and are timing out
319
+ // 2a) system error: JSON is corrupt
320
+ // i) unwitting actor
321
+ // ii) corrupt hardware/network/transport/etc
322
+ // 3b) system error: Redis unable to accept `xadd` request
323
+ // 4c) system error: Redis unable to accept `xdel`/`xack` request
299
324
  this.logger.error('stream-message-max-delivery-count-exceeded', { id, stream, group, consumer, code: enums_1.HMSH_CODE_UNACKED, count });
300
325
  const streamData = reclaimedMessage[0]?.[1]?.[1];
326
+ //fatal risk point 1 of 3): json is corrupt
301
327
  const [err, input] = this.parseStreamData(streamData);
302
328
  if (err) {
303
329
  return this.logger.error('expire-unacknowledged-parse-fatal-error', { id, err });
@@ -311,9 +337,11 @@ class Router {
311
337
  telemetry = new telemetry_1.TelemetryService(this.appId);
312
338
  telemetry.startStreamSpan(input, stream_1.StreamRole.SYSTEM);
313
339
  telemetry.setStreamError(`Stream Message Max Delivery Count Exceeded`);
340
+ //fatal risk point 2 of 3): unable to publish error message (to notify the parent job)
314
341
  const output = this.structureUnacknowledgedError(input);
315
342
  messageId = await this.publishResponse(input, output);
316
343
  telemetry.setStreamAttributes({ 'app.worker.mid': messageId });
344
+ //fatal risk point 3 of 3): unable to ack and delete stream message
317
345
  await this.ackAndDelete(stream, group, id);
318
346
  }
319
347
  catch (err) {
@@ -49,6 +49,7 @@ class SerializerService {
49
49
  return this.dIds[activityId];
50
50
  }
51
51
  else if ('$ADJACENT' in this.dIds) {
52
+ //else=> pre-authorizing adjacent activity entry
52
53
  return this.dIds['$ADJACENT'];
53
54
  }
54
55
  return ',0';
@@ -117,6 +118,7 @@ class SerializerService {
117
118
  result[shortDimensionalKey] = source[key];
118
119
  }
119
120
  else if (!(key in result) && this.isLiteralKeyType(key)) {
121
+ //mark (-) and search (_)
120
122
  result[key] = source[key];
121
123
  }
122
124
  }
@@ -140,6 +142,7 @@ class SerializerService {
140
142
  const inflateWithMap = (abbreviationMap, id) => {
141
143
  const reversedAbbreviationMap = this.getReverseKeyMap(abbreviationMap, id);
142
144
  for (let key in result) {
145
+ //strip dimensional index from key
143
146
  const shortKey = key.split(',')[0];
144
147
  let longKey = reversedAbbreviationMap.get(shortKey);
145
148
  if (longKey) {
@@ -156,22 +159,36 @@ class SerializerService {
156
159
  }
157
160
  return result;
158
161
  }
162
+ //stringify: convert a multi-dimensional document to a 2-d hash
159
163
  stringify(document) {
160
164
  let result = {};
161
165
  for (let key in document) {
162
166
  let value = SerializerService.toString(document[key]);
163
167
  if (value) {
168
+ // if (/^:*[a-zA-Z]{2}$/.test(value)) {
169
+ // value = ':' + value;
170
+ // } else if (this.symValReverseMaps.has(value)) {
171
+ // value = this.symValReverseMaps.get(value);
172
+ // }
164
173
  result[key] = value;
165
174
  }
166
175
  }
167
176
  return result;
168
177
  }
178
+ //parse: convert a 2-d hash to a multi-dimensional document
169
179
  parse(document) {
170
180
  let result = {};
171
181
  for (let [key, value] of Object.entries(document)) {
172
182
  if (value === undefined || value === null)
173
183
  continue;
184
+ // if (/^:+[a-zA-Z]{2}$/.test(value)) {
185
+ // result[key] = value.slice(1);
186
+ // } else {
187
+ // if (value?.length === 2 && this.symValMaps.has(value)) {
188
+ // value = this.symValMaps.get(value);
189
+ // }
174
190
  result[key] = SerializerService.fromString(value);
191
+ // }
175
192
  }
176
193
  return result;
177
194
  }
@@ -204,20 +221,20 @@ class SerializerService {
204
221
  const prefix = value.slice(0, 2);
205
222
  const rest = value.slice(2);
206
223
  switch (prefix) {
207
- case '/t':
224
+ case '/t': // boolean true
208
225
  return true;
209
- case '/f':
226
+ case '/f': // boolean false
210
227
  return false;
211
- case '/d':
228
+ case '/d': // number
212
229
  return Number(rest);
213
- case '/n':
230
+ case '/n': // null
214
231
  return null;
215
- case '/s':
232
+ case '/s': // object (JSON string)
216
233
  if (dateReg.exec(rest)) {
217
234
  return new Date(JSON.parse(rest));
218
235
  }
219
236
  return JSON.parse(rest);
220
- default:
237
+ default: // string
221
238
  return value;
222
239
  }
223
240
  }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * The Cache is a key/value store and used to store commonly accessed Redis metadata
3
+ * (mainly the execution rules for the app) to save time accessing them as they
4
+ * are immutable per verison. Rules are only ejected when a new version
5
+ * (a new distributed executable) is deployed to the quorum
6
+ * and the cache is invalidated/cleared of the prior version.
7
+ */
1
8
  import { ActivityType } from "../../types/activity";
2
9
  import { HookRule } from "../../types/hook";
3
10
  import { HotMeshApp, HotMeshSettings } from "../../types/hotmesh";
@@ -14,7 +21,19 @@ declare class Cache {
14
21
  transitions: Record<string, Record<string, unknown>>;
15
22
  hookRules: Record<string, Record<string, HookRule[]>>;
16
23
  workItems: Record<string, string>;
24
+ /**
25
+ * The cache is ALWAYS initialized with HotMeshSettings. The other parameters are optional.
26
+ * @param settings
27
+ * @param apps
28
+ * @param schemas
29
+ * @param subscriptions
30
+ * @param transitions
31
+ * @param hookRules
32
+ */
17
33
  constructor(appId: string, settings: HotMeshSettings, apps?: Record<string, HotMeshApp>, schemas?: Record<string, ActivityType>, subscriptions?: Record<string, Record<string, string>>, symbols?: Record<string, Symbols>, symvals?: Record<string, Symbols>, transitions?: Record<string, Record<string, unknown>>, hookRules?: Record<string, Record<string, HookRule[]>>, workItems?: Record<string, string>);
34
+ /**
35
+ * invalidate the cache; settings are not invalidated!
36
+ */
18
37
  invalidate(): void;
19
38
  getSettings(): HotMeshSettings;
20
39
  setSettings(settings: HotMeshSettings): void;
@@ -1,7 +1,23 @@
1
1
  "use strict";
2
+ /**
3
+ * The Cache is a key/value store and used to store commonly accessed Redis metadata
4
+ * (mainly the execution rules for the app) to save time accessing them as they
5
+ * are immutable per verison. Rules are only ejected when a new version
6
+ * (a new distributed executable) is deployed to the quorum
7
+ * and the cache is invalidated/cleared of the prior version.
8
+ */
2
9
  Object.defineProperty(exports, "__esModule", { value: true });
3
10
  exports.Cache = void 0;
4
11
  class Cache {
12
+ /**
13
+ * The cache is ALWAYS initialized with HotMeshSettings. The other parameters are optional.
14
+ * @param settings
15
+ * @param apps
16
+ * @param schemas
17
+ * @param subscriptions
18
+ * @param transitions
19
+ * @param hookRules
20
+ */
5
21
  constructor(appId, settings, apps = {}, schemas = {}, subscriptions = {}, symbols = {}, symvals = {}, transitions = {}, hookRules = {}, workItems = {}) {
6
22
  this.appId = appId;
7
23
  this.settings = settings;
@@ -14,6 +30,9 @@ class Cache {
14
30
  this.hookRules = hookRules;
15
31
  this.workItems = workItems;
16
32
  }
33
+ /**
34
+ * invalidate the cache; settings are not invalidated!
35
+ */
17
36
  invalidate() {
18
37
  this.apps = {};
19
38
  this.schemas = {};
@@ -24,6 +24,7 @@ class IORedisStoreService extends index_1.StoreService {
24
24
  return response;
25
25
  }
26
26
  hGetAllResult(result) {
27
+ //ioredis response signature is [null, {}] or [null, null]
27
28
  return result[1];
28
29
  }
29
30
  async addTaskQueues(keys) {
@@ -41,6 +41,11 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
41
41
  zRangeByScore(key: string, score: number | string, value: string | number): Promise<string | null>;
42
42
  mintKey(type: KeyType, params: KeyStoreParams): string;
43
43
  invalidateCache(): void;
44
+ /**
45
+ * At any given time only a single engine will
46
+ * check for and process work items in the
47
+ * time and signal task queues.
48
+ */
44
49
  reserveScoutRole(scoutType: 'time' | 'signal' | 'activate', delay?: number): Promise<boolean>;
45
50
  releaseScoutRole(scoutType: 'time' | 'signal' | 'activate'): Promise<boolean>;
46
51
  getSettings(bCreate?: boolean): Promise<HotMeshSettings>;
@@ -59,7 +64,16 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
59
64
  setApp(id: string, version: string): Promise<HotMeshApp>;
60
65
  activateAppVersion(id: string, version: string): Promise<boolean>;
61
66
  registerAppVersion(appId: string, version: string): Promise<any>;
67
+ /**
68
+ * Registers the job, `jobId`, with `originJobId`. In the future,
69
+ * when `originJobId` is interrupted/expired, the items in the
70
+ * list (added via RPUSH) will be interrupted/expired (removed via LPOPed).
71
+ */
62
72
  registerJobDependency(depType: WorkListTaskType, originJobId: string, topic: string, jobId: string, gId: string, pd?: string, multi?: U): Promise<any>;
73
+ /**
74
+ * Ensures a `hook signal` is delisted when its parent activity/job
75
+ * is interrupted/expired.
76
+ */
63
77
  registerSignalDependency(jobId: string, signalKey: string, dad: string, multi?: U): Promise<any>;
64
78
  setStats(jobKey: string, jobId: string, dateTime: string, stats: StatsType, appVersion: AppVID, multi?: U): Promise<any>;
65
79
  hGetAllResult(result: any): any;
@@ -68,10 +82,24 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
68
82
  setStatus(collationKeyStatus: number, jobId: string, appId: string, multi?: U): Promise<any>;
69
83
  getStatus(jobId: string, appId: string): Promise<number>;
70
84
  setState({ ...state }: StringAnyType, status: number | null, jobId: string, symbolNames: string[], dIds: StringStringType, multi?: U): Promise<string>;
85
+ /**
86
+ * Returns custom search fields and values.
87
+ * NOTE: The `fields` param should NOT prefix items with an underscore.
88
+ */
71
89
  getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
72
90
  getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
73
91
  getRaw(jobId: string): Promise<StringStringType>;
92
+ /**
93
+ * collate is a generic method for incrementing a value in a hash
94
+ * in order to track their progress during processing.
95
+ */
74
96
  collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, multi?: U): Promise<number>;
97
+ /**
98
+ * synthentic collation affects those activities in the graph
99
+ * that represent the synthetic DAG that was materialized during compilation;
100
+ * Synthetic targeting ensures that re-entry due to failure can be distinguished from
101
+ * purposeful re-entry.
102
+ */
75
103
  collateSynthetic(jobId: string, guid: string, amount: number, multi?: U): Promise<number>;
76
104
  setStateNX(jobId: string, appId: string): Promise<boolean>;
77
105
  getSchema(activityId: string, appVersion: AppVID): Promise<ActivityType>;
@@ -92,11 +120,38 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
92
120
  deleteProcessedTaskQueue(workItemKey: string, key: string, processedKey: string, scrub?: boolean): Promise<void>;
93
121
  processTaskQueue(sourceKey: string, destinationKey: string): Promise<any>;
94
122
  expireJob(jobId: string, inSeconds: number): Promise<void>;
123
+ /**
124
+ * register the descendants of an expired origin flow to be
125
+ * expired at a future date; options indicate whether this
126
+ * is a standard `expire` or an `interrupt`
127
+ */
95
128
  registerDependenciesForCleanup(jobId: string, deletionTime: number, options: JobCompletionOptions): Promise<void>;
96
129
  getDependencies(jobId: string): Promise<string[]>;
130
+ /**
131
+ * registers a hook activity to be awakened (uses ZSET to
132
+ * store the 'sleep group' and LIST to store the events
133
+ * for the given sleep group. Sleep groups are
134
+ * organized into 'n'-second blocks (LISTS))
135
+ */
97
136
  registerTimeHook(jobId: string, gId: string, activityId: string, type: WorkListTaskType, deletionTime: number, dad: string, multi?: U): Promise<void>;
98
137
  getNextTask(listKey?: string): Promise<[listKey: string, jobId: string, gId: string, activityId: string, type: WorkListTaskType] | boolean>;
138
+ /**
139
+ * when processing time jobs, the target LIST ID returned
140
+ * from the ZSET query can be prefixed to denote what to
141
+ * do with the work list. (not everything is known in advance,
142
+ * so the ZSET key defines HOW to approach the work in the
143
+ * generic LIST (lists typically contain target job ids)
144
+ * @param {string} listKey - composite key
145
+ */
99
146
  resolveTaskKeyContext(listKey: string): [WorkListTaskType, string];
147
+ /**
148
+ * Interrupts a job and sets sets a job error (410), if 'throw'!=false.
149
+ * This method is called by the engine and not by an activity and is
150
+ * followed by a call to execute job completion/cleanup tasks
151
+ * associated with a job completion event.
152
+ *
153
+ * Todo: move most of this logic to the engine (too much logic for the store)
154
+ */
100
155
  interrupt(topic: string, jobId: string, options?: JobInterruptOptions): Promise<void>;
101
156
  scrub(jobId: string): Promise<void>;
102
157
  findJobs(queryString?: string, limit?: number, batchSize?: number, cursor?: string): Promise<[string, string[]]>;