@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
@@ -10,11 +10,47 @@ export declare class WorkflowHandleService {
10
10
  workflowId: string;
11
11
  constructor(hotMesh: HotMesh, workflowTopic: string, workflowId: string);
12
12
  export(options?: ExportOptions): Promise<DurableJobExport>;
13
+ /**
14
+ * Sends a signal to the workflow. This is a way to send
15
+ * a message to a workflow that is paused due to having
16
+ * executed `Durable.workflow.waitFor`. The workflow
17
+ * will awaken if no other signals are pending.
18
+ */
13
19
  signal(signalId: string, data: Record<any, any>): Promise<void>;
20
+ /**
21
+ * Returns the job state of the workflow. If the workflow has completed
22
+ * this is also the job output. If the workflow is still running, this
23
+ * is the current state of the job, but it may change depending upon
24
+ * the activities that remain.
25
+ */
14
26
  state(metadata?: boolean): Promise<Record<string, any>>;
27
+ /**
28
+ * Returns the current search state of the workflow. This is
29
+ * different than the job state or individual activity state.
30
+ * Search state represents name/value pairs that were added
31
+ * to the workflow. As the workflow is stored in a Redis hash,
32
+ * this is a way to store additional data that is indexed
33
+ * and searchable using the RediSearch module.
34
+ */
15
35
  queryState(fields: string[]): Promise<Record<string, any>>;
36
+ /**
37
+ * Returns the current status of the workflow. This is a semaphore
38
+ * value that represents the current state of the workflow, where
39
+ * 0 is complete and a negative value represents that the flow was
40
+ * interrupted.
41
+ */
16
42
  status(): Promise<number>;
43
+ /**
44
+ * Interrupts a running workflow. Standard Job Completion tasks will
45
+ * run. Subscribers will be notified and the job hash will be expired.
46
+ */
17
47
  interrupt(options?: JobInterruptOptions): Promise<string>;
48
+ /**
49
+ * Waits for the workflow to complete and returns the result. If
50
+ * the workflow response includes an error, this method will rethrow
51
+ * the error, including the stack trace if available.
52
+ * Wrap calls in a try/catch as necessary to avoid unhandled exceptions.
53
+ */
18
54
  result<T>(config?: {
19
55
  state?: boolean;
20
56
  throwOnError?: boolean;
@@ -12,9 +12,21 @@ class WorkflowHandleService {
12
12
  async export(options) {
13
13
  return this.exporter.export(this.workflowId, options);
14
14
  }
15
+ /**
16
+ * Sends a signal to the workflow. This is a way to send
17
+ * a message to a workflow that is paused due to having
18
+ * executed `Durable.workflow.waitFor`. The workflow
19
+ * will awaken if no other signals are pending.
20
+ */
15
21
  async signal(signalId, data) {
16
22
  await this.hotMesh.hook(`${this.hotMesh.appId}.wfs.signal`, { id: signalId, data });
17
23
  }
24
+ /**
25
+ * Returns the job state of the workflow. If the workflow has completed
26
+ * this is also the job output. If the workflow is still running, this
27
+ * is the current state of the job, but it may change depending upon
28
+ * the activities that remain.
29
+ */
18
30
  async state(metadata = false) {
19
31
  const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
20
32
  if (!state.data && state.metadata.err) {
@@ -22,25 +34,56 @@ class WorkflowHandleService {
22
34
  }
23
35
  return metadata ? state : state.data;
24
36
  }
37
+ /**
38
+ * Returns the current search state of the workflow. This is
39
+ * different than the job state or individual activity state.
40
+ * Search state represents name/value pairs that were added
41
+ * to the workflow. As the workflow is stored in a Redis hash,
42
+ * this is a way to store additional data that is indexed
43
+ * and searchable using the RediSearch module.
44
+ */
25
45
  async queryState(fields) {
26
46
  return await this.hotMesh.getQueryState(this.workflowId, fields);
27
47
  }
48
+ /**
49
+ * Returns the current status of the workflow. This is a semaphore
50
+ * value that represents the current state of the workflow, where
51
+ * 0 is complete and a negative value represents that the flow was
52
+ * interrupted.
53
+ */
28
54
  async status() {
29
55
  return await this.hotMesh.getStatus(this.workflowId);
30
56
  }
57
+ /**
58
+ * Interrupts a running workflow. Standard Job Completion tasks will
59
+ * run. Subscribers will be notified and the job hash will be expired.
60
+ */
31
61
  async interrupt(options) {
32
62
  return await this.hotMesh.interrupt(`${this.hotMesh.appId}.execute`, this.workflowId, options);
33
63
  }
64
+ /**
65
+ * Waits for the workflow to complete and returns the result. If
66
+ * the workflow response includes an error, this method will rethrow
67
+ * the error, including the stack trace if available.
68
+ * Wrap calls in a try/catch as necessary to avoid unhandled exceptions.
69
+ */
34
70
  async result(config) {
35
71
  const topic = `${this.hotMesh.appId}.executed.${this.workflowId}`;
36
72
  let isResolved = false;
37
73
  return new Promise(async (resolve, reject) => {
74
+ /**
75
+ * rejects/resolves the promise based on the `throwOnError`
76
+ * default behavior is to throw if error
77
+ */
38
78
  const safeReject = (err) => {
39
79
  if (config?.throwOnError === false) {
40
80
  return resolve(err);
41
81
  }
42
82
  reject(err);
43
83
  };
84
+ /**
85
+ * Common completion function that unsubscribes from the topic/returns
86
+ */
44
87
  const complete = async (response, err) => {
45
88
  if (isResolved)
46
89
  return;
@@ -63,6 +106,7 @@ class WorkflowHandleService {
63
106
  }
64
107
  resolve(response);
65
108
  };
109
+ //more expensive; fetches the entire job, not just the `status`
66
110
  if (config?.state) {
67
111
  const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
68
112
  if (state?.data?.done && !state.data?.$error) {
@@ -75,6 +119,7 @@ class WorkflowHandleService {
75
119
  return complete(null, JSON.parse(state.metadata.err));
76
120
  }
77
121
  }
122
+ //subscribe to 'done' topic
78
123
  this.hotMesh.sub(topic, async (_topic, state) => {
79
124
  this.hotMesh.unsub(topic);
80
125
  if (state.data.done && !state.data?.$error) {
@@ -88,6 +133,7 @@ class WorkflowHandleService {
88
133
  return await complete(null, error);
89
134
  }
90
135
  });
136
+ //check state in case completed during wiring
91
137
  const status = await this.hotMesh.getStatus(this.workflowId);
92
138
  if (status <= 0) {
93
139
  await complete();
@@ -10,6 +10,10 @@ export declare const Durable: {
10
10
  Search: typeof Search;
11
11
  Worker: typeof WorkerService;
12
12
  workflow: typeof WorkflowService;
13
+ /**
14
+ * Shutdown everything. All connections, workers, and clients will be closed.
15
+ * Include in your signal handlers to ensure a clean shutdown.
16
+ */
13
17
  shutdown(): Promise<void>;
14
18
  };
15
19
  export type { ContextType };
@@ -13,6 +13,10 @@ exports.Durable = {
13
13
  Search: search_1.Search,
14
14
  Worker: worker_1.WorkerService,
15
15
  workflow: workflow_1.WorkflowService,
16
+ /**
17
+ * Shutdown everything. All connections, workers, and clients will be closed.
18
+ * Include in your signal handlers to ensure a clean shutdown.
19
+ */
16
20
  async shutdown() {
17
21
  await client_1.ClientService.shutdown();
18
22
  await worker_1.WorkerService.shutdown();
@@ -1,4 +1,33 @@
1
+ /**
2
+ *********** HOTMESH 'DURABLE' MODULE APPLICATION GRAPH **********
3
+ *
4
+ * This HotMesh application spec uses 50 activities and 25 transitions
5
+ * to model and emulate the Temporal Application & Query servers using
6
+ * Redis as the backend.
7
+ *
8
+ * It's particularly useful for organizations with high-speed, high-volume
9
+ * use cases as it uses in-memory Redis Streams for transactional,
10
+ * workflow processing, while adhering to Temporal's developer-friendly syntax.
11
+ *
12
+ * This YAML file can also serve as a useful starting point for building
13
+ * Integration/BPM/Workflow servers in general (MuleSoft, etc) without the need
14
+ * for a physical application server.
15
+ *
16
+ * Possible use cases include:
17
+ * * Orchestration servers
18
+ * * Integration servers
19
+ * * BPMN engines
20
+ * * Reentrant process servers
21
+ * * Service Meshes
22
+ * * Master Data Management systems
23
+ */
1
24
  declare const APP_VERSION = "1";
2
25
  declare const APP_ID = "durable";
26
+ /**
27
+ * returns a new durable workflow schema
28
+ * @param {string} app - app name (e.g., 'durable')
29
+ * @param {string} version - number as string (e.g., '1')
30
+ * @returns {string} HotMesh App YAML
31
+ */
3
32
  declare const getWorkflowYAML: (app: string, version: string) => string;
4
33
  export { getWorkflowYAML, APP_VERSION, APP_ID, };
@@ -1,10 +1,39 @@
1
1
  "use strict";
2
+ /**
3
+ *********** HOTMESH 'DURABLE' MODULE APPLICATION GRAPH **********
4
+ *
5
+ * This HotMesh application spec uses 50 activities and 25 transitions
6
+ * to model and emulate the Temporal Application & Query servers using
7
+ * Redis as the backend.
8
+ *
9
+ * It's particularly useful for organizations with high-speed, high-volume
10
+ * use cases as it uses in-memory Redis Streams for transactional,
11
+ * workflow processing, while adhering to Temporal's developer-friendly syntax.
12
+ *
13
+ * This YAML file can also serve as a useful starting point for building
14
+ * Integration/BPM/Workflow servers in general (MuleSoft, etc) without the need
15
+ * for a physical application server.
16
+ *
17
+ * Possible use cases include:
18
+ * * Orchestration servers
19
+ * * Integration servers
20
+ * * BPMN engines
21
+ * * Reentrant process servers
22
+ * * Service Meshes
23
+ * * Master Data Management systems
24
+ */
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.APP_ID = exports.APP_VERSION = exports.getWorkflowYAML = void 0;
4
27
  const APP_VERSION = '1';
5
28
  exports.APP_VERSION = APP_VERSION;
6
29
  const APP_ID = 'durable';
7
30
  exports.APP_ID = APP_ID;
31
+ /**
32
+ * returns a new durable workflow schema
33
+ * @param {string} app - app name (e.g., 'durable')
34
+ * @param {string} version - number as string (e.g., '1')
35
+ * @returns {string} HotMesh App YAML
36
+ */
8
37
  const getWorkflowYAML = (app, version) => {
9
38
  return `app:
10
39
  id: ${app}
@@ -10,14 +10,111 @@ export declare class Search {
10
10
  store: StoreService<RedisClient, RedisMulti> | null;
11
11
  cachedFields: Record<string, string>;
12
12
  constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string);
13
+ /**
14
+ * Prefixes the key with an underscore to keep separate from the
15
+ * activity and job history (and searchable via HKEYS)
16
+ * @param {string} key - the key to be sanitized. Wrap in quotes to avoid sanitization.
17
+ * @returns {string} - the sanitized key
18
+ * @private
19
+ */
13
20
  safeKey(key: string): string;
21
+ /**
22
+ * For those deployments with a redis stack backend (with the FT module),
23
+ * this method will configure the search index for the workflow. For all
24
+ * others, this method will exit/fail gracefully and not index
25
+ * the fields in the HASH. All values are searchable via HKEYS/HSC/HGET
26
+ * @param {HotMesh} hotMeshClient - the hotmesh client
27
+ * @param {WorkflowSearchOptions} search - the search options
28
+ * @returns {Promise<void>}
29
+ * @example
30
+ * const search = {
31
+ * index: 'my_search_index',
32
+ * prefix: ['my_workflow_prefix'],
33
+ * schema: {
34
+ * field1: { type: 'TEXT', sortable: true },
35
+ * field2: { type: 'NUMERIC', sortable: true }
36
+ * }
37
+ * }
38
+ * await Search.configureSearchIndex(hotMeshClient, search);
39
+ */
14
40
  static configureSearchIndex(hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void>;
41
+ /**
42
+ * For those deployments with a redis stack backend (with the FT module),
43
+ * this method will list all search indexes.
44
+ *
45
+ * @param {HotMesh} hotMeshClient - the hotmesh client
46
+ * @returns {Promise<string[]>} - the list of search indexes
47
+ * @example
48
+ * const searchIndexes = await Search.listSearchIndexes(hotMeshClient);
49
+ */
15
50
  static listSearchIndexes(hotMeshClient: HotMesh): Promise<string[]>;
51
+ /**
52
+ * increments the index to return a unique search session guid when
53
+ * calling any method that produces side effects (changes the value)
54
+ * @private
55
+ */
16
56
  getSearchSessionGuid(): string;
57
+ /**
58
+ * Sets the fields listed in args. Returns the
59
+ * count of new fields that were set (does not
60
+ * count fields that were updated)
61
+ * @param args
62
+ * @returns {number}
63
+ * @example
64
+ * const search = new Search();
65
+ * const count = await search.set('field1', 'value1', 'field2', 'value2');
66
+ */
17
67
  set(...args: string[]): Promise<number>;
68
+ /**
69
+ * Returns the value of the field in the HASH stored at key.
70
+ * @param key
71
+ * @returns {string}
72
+ * @example
73
+ * const search = new Search();
74
+ * const value = await search.get('field1');
75
+ */
18
76
  get(key: string): Promise<string>;
77
+ /**
78
+ * Returns the values of all specified fields in the HASH stored at key.
79
+ * @param args
80
+ * @returns
81
+ */
19
82
  mget(...args: string[]): Promise<string[]>;
83
+ /**
84
+ * Deletes the fields provided as args. Returns the
85
+ * count of fields that were deleted.
86
+ *
87
+ * @param args
88
+ * @returns {number}
89
+ * @example
90
+ * sont search = new Search();
91
+ * const count = await search.del('field1', 'field2', 'field3');
92
+ */
20
93
  del(...args: string[]): Promise<number | void>;
94
+ /**
95
+ * Increments the value of a float field by the given amount. Returns the
96
+ * new value of the field after the increment. Pass a negative
97
+ * number to decrement the value.
98
+ *
99
+ * @param key - the key to increment
100
+ * @param val - the value to increment by
101
+ * @returns {number} - the new value
102
+ * @example
103
+ * const search = new Search();
104
+ * const count = await search.incr('field1', 1.5);
105
+ */
21
106
  incr(key: string, val: number): Promise<number>;
107
+ /**
108
+ * Multiplies the value of a field by the given amount. Returns the
109
+ * new value of the field after the multiplication. NOTE:
110
+ * this is exponential multiplication.
111
+ *
112
+ * @param key - the key to multiply
113
+ * @param val - the value to multiply by
114
+ * @returns {number} - the new product of the multiplication
115
+ * @example
116
+ * const search = new Search();
117
+ * const product = await search.mult('field1', 1.5);
118
+ */
22
119
  mult(key: string, val: number): Promise<number>;
23
120
  }
@@ -16,12 +16,38 @@ class Search {
16
16
  this.hotMeshClient = hotMeshClient;
17
17
  this.store = hotMeshClient.engine.store;
18
18
  }
19
+ /**
20
+ * Prefixes the key with an underscore to keep separate from the
21
+ * activity and job history (and searchable via HKEYS)
22
+ * @param {string} key - the key to be sanitized. Wrap in quotes to avoid sanitization.
23
+ * @returns {string} - the sanitized key
24
+ * @private
25
+ */
19
26
  safeKey(key) {
20
27
  if (key.startsWith('"')) {
21
28
  return key.slice(1, -1);
22
29
  }
23
30
  return `_${key}`;
24
31
  }
32
+ /**
33
+ * For those deployments with a redis stack backend (with the FT module),
34
+ * this method will configure the search index for the workflow. For all
35
+ * others, this method will exit/fail gracefully and not index
36
+ * the fields in the HASH. All values are searchable via HKEYS/HSC/HGET
37
+ * @param {HotMesh} hotMeshClient - the hotmesh client
38
+ * @param {WorkflowSearchOptions} search - the search options
39
+ * @returns {Promise<void>}
40
+ * @example
41
+ * const search = {
42
+ * index: 'my_search_index',
43
+ * prefix: ['my_workflow_prefix'],
44
+ * schema: {
45
+ * field1: { type: 'TEXT', sortable: true },
46
+ * field2: { type: 'NUMERIC', sortable: true }
47
+ * }
48
+ * }
49
+ * await Search.configureSearchIndex(hotMeshClient, search);
50
+ */
25
51
  static async configureSearchIndex(hotMeshClient, search) {
26
52
  if (search?.schema) {
27
53
  const store = hotMeshClient.engine.store;
@@ -57,6 +83,15 @@ class Search {
57
83
  }
58
84
  }
59
85
  }
86
+ /**
87
+ * For those deployments with a redis stack backend (with the FT module),
88
+ * this method will list all search indexes.
89
+ *
90
+ * @param {HotMesh} hotMeshClient - the hotmesh client
91
+ * @returns {Promise<string[]>} - the list of search indexes
92
+ * @example
93
+ * const searchIndexes = await Search.listSearchIndexes(hotMeshClient);
94
+ */
60
95
  static async listSearchIndexes(hotMeshClient) {
61
96
  try {
62
97
  const store = hotMeshClient.engine.store;
@@ -68,9 +103,25 @@ class Search {
68
103
  return [];
69
104
  }
70
105
  }
106
+ /**
107
+ * increments the index to return a unique search session guid when
108
+ * calling any method that produces side effects (changes the value)
109
+ * @private
110
+ */
71
111
  getSearchSessionGuid() {
112
+ //return the search session as it would exist in the search session index
72
113
  return `${this.searchSessionId}-${this.searchSessionIndex++}-`;
73
114
  }
115
+ /**
116
+ * Sets the fields listed in args. Returns the
117
+ * count of new fields that were set (does not
118
+ * count fields that were updated)
119
+ * @param args
120
+ * @returns {number}
121
+ * @example
122
+ * const search = new Search();
123
+ * const count = await search.set('field1', 'value1', 'field2', 'value2');
124
+ */
74
125
  async set(...args) {
75
126
  const ssGuid = this.getSearchSessionGuid();
76
127
  const store = storage_1.asyncLocalStorage.getStore();
@@ -87,9 +138,18 @@ class Search {
87
138
  return Number(replay[ssGuid]);
88
139
  }
89
140
  const fieldCount = await this.store.exec('HSET', this.jobId, ...safeArgs);
141
+ //no need to wait; set this interim value in the replay
90
142
  this.store.exec('HSET', this.jobId, ssGuid, fieldCount.toString());
91
143
  return Number(fieldCount);
92
144
  }
145
+ /**
146
+ * Returns the value of the field in the HASH stored at key.
147
+ * @param key
148
+ * @returns {string}
149
+ * @example
150
+ * const search = new Search();
151
+ * const value = await search.get('field1');
152
+ */
93
153
  async get(key) {
94
154
  try {
95
155
  if (key in this.cachedFields) {
@@ -104,6 +164,11 @@ class Search {
104
164
  return '';
105
165
  }
106
166
  }
167
+ /**
168
+ * Returns the values of all specified fields in the HASH stored at key.
169
+ * @param args
170
+ * @returns
171
+ */
107
172
  async mget(...args) {
108
173
  let isCached = true;
109
174
  const values = [];
@@ -134,6 +199,16 @@ class Search {
134
199
  return [];
135
200
  }
136
201
  }
202
+ /**
203
+ * Deletes the fields provided as args. Returns the
204
+ * count of fields that were deleted.
205
+ *
206
+ * @param args
207
+ * @returns {number}
208
+ * @example
209
+ * sont search = new Search();
210
+ * const count = await search.del('field1', 'field2', 'field3');
211
+ */
137
212
  async del(...args) {
138
213
  const ssGuid = this.getSearchSessionGuid();
139
214
  const store = storage_1.asyncLocalStorage.getStore();
@@ -152,6 +227,18 @@ class Search {
152
227
  this.store.exec('HSET', this.jobId, ssGuid, formattedResponse.toString());
153
228
  return formattedResponse;
154
229
  }
230
+ /**
231
+ * Increments the value of a float field by the given amount. Returns the
232
+ * new value of the field after the increment. Pass a negative
233
+ * number to decrement the value.
234
+ *
235
+ * @param key - the key to increment
236
+ * @param val - the value to increment by
237
+ * @returns {number} - the new value
238
+ * @example
239
+ * const search = new Search();
240
+ * const count = await search.incr('field1', 1.5);
241
+ */
155
242
  async incr(key, val) {
156
243
  delete this.cachedFields[key];
157
244
  const ssGuid = this.getSearchSessionGuid();
@@ -164,6 +251,18 @@ class Search {
164
251
  this.store.exec('HSET', this.jobId, ssGuid, num.toString());
165
252
  return Number(num);
166
253
  }
254
+ /**
255
+ * Multiplies the value of a field by the given amount. Returns the
256
+ * new value of the field after the multiplication. NOTE:
257
+ * this is exponential multiplication.
258
+ *
259
+ * @param key - the key to multiply
260
+ * @param val - the value to multiply by
261
+ * @returns {number} - the new product of the multiplication
262
+ * @example
263
+ * const search = new Search();
264
+ * const product = await search.mult('field1', 1.5);
265
+ */
167
266
  async mult(key, val) {
168
267
  delete this.cachedFields[key];
169
268
  const ssGuid = this.getSearchSessionGuid();
@@ -9,11 +9,11 @@ const ms_1 = __importDefault(require("ms"));
9
9
  const enums_1 = require("../../modules/enums");
10
10
  const errors_1 = require("../../modules/errors");
11
11
  const storage_1 = require("../../modules/storage");
12
+ const utils_1 = require("../../modules/utils");
12
13
  const factory_1 = require("./schemas/factory");
13
14
  const hotmesh_1 = require("../hotmesh");
14
15
  const search_1 = require("./search");
15
16
  const stream_1 = require("../../types/stream");
16
- const utils_1 = require("../../modules/utils");
17
17
  class WorkerService {
18
18
  static async activateWorkflow(hotMesh) {
19
19
  const app = await hotMesh.engine.store.getApp(hotMesh.engine.appId);
@@ -61,6 +61,7 @@ class WorkerService {
61
61
  const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
62
62
  const activityTopic = `${baseTopic}-activity`;
63
63
  const workflowTopic = `${baseTopic}`;
64
+ //initialize supporting workflows
64
65
  const worker = new WorkerService();
65
66
  worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
66
67
  worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
@@ -103,9 +104,11 @@ class WorkerService {
103
104
  WorkerService.instances.set(activityTopic, hotMeshWorker);
104
105
  return hotMeshWorker;
105
106
  }
107
+ //this is the linked worker function in the reentrant workflow test
106
108
  wrapActivityFunctions() {
107
109
  return async (data) => {
108
110
  try {
111
+ //always run the activity function when instructed; return the response
109
112
  const activityInput = data.data;
110
113
  const activityName = activityInput.activityName;
111
114
  const activityFunction = WorkerService.activityRegistry[activityName];
@@ -121,6 +124,8 @@ class WorkerService {
121
124
  if (!(err instanceof errors_1.DurableTimeoutError) &&
122
125
  !(err instanceof errors_1.DurableMaxedError) &&
123
126
  !(err instanceof errors_1.DurableFatalError)) {
127
+ //use code 599 as a proxy for all retryable errors
128
+ // (basically anything not 596, 597, 598)
124
129
  return {
125
130
  status: stream_1.StreamStatus.SUCCESS,
126
131
  code: enums_1.HMSH_CODE_DURABLE_RETRYABLE,
@@ -135,6 +140,8 @@ class WorkerService {
135
140
  };
136
141
  }
137
142
  return {
143
+ //always returrn success (the Durable module is just fine);
144
+ // it's the user's function that has failed
138
145
  status: stream_1.StreamStatus.SUCCESS,
139
146
  code: err.code,
140
147
  stack: err.stack,
@@ -172,8 +179,10 @@ class WorkerService {
172
179
  wrapWorkflowFunction(workflowFunction, workflowTopic, config) {
173
180
  return async (data) => {
174
181
  const counter = { counter: 0 };
175
- const interruptionRegistry = [];
182
+ const interruptionRegistry = new Array();
183
+ let isProcessing = false;
176
184
  try {
185
+ //incoming data payload has arguments and workflowId
177
186
  const workflowInput = data.data;
178
187
  const context = new Map();
179
188
  context.set('canRetry', workflowInput.canRetry);
@@ -183,14 +192,20 @@ class WorkerService {
183
192
  context.set('raw', data);
184
193
  context.set('workflowId', workflowInput.workflowId);
185
194
  if (workflowInput.originJobId) {
195
+ //if present there is an origin job to which this job is subordinated;
196
+ // garbage collect (expire) this job when originJobId is expired
186
197
  context.set('originJobId', workflowInput.originJobId);
187
198
  }
188
199
  let replayQuery = '';
189
200
  if (workflowInput.workflowDimension) {
201
+ //every hook function runs in an isolated dimension controlled
202
+ //by the index assigned when the signal was received; even if the
203
+ //hook function re-runs, its scope will always remain constant
190
204
  context.set('workflowDimension', workflowInput.workflowDimension);
191
205
  replayQuery = `-*${workflowInput.workflowDimension}-*`;
192
206
  }
193
207
  else {
208
+ //last letter of words like 'hook', 'sleep', 'wait', 'signal', 'search', 'start', 'proxy', 'child', 'collator'
194
209
  replayQuery = '-*[ehklptydr]-*';
195
210
  }
196
211
  context.set('workflowTopic', workflowTopic);
@@ -200,7 +215,7 @@ class WorkerService {
200
215
  const store = this.workflowRunner.engine.store;
201
216
  const [cursor, replay] = await store.findJobFields(workflowInput.workflowId, replayQuery, 50000, 5000);
202
217
  context.set('replay', replay);
203
- context.set('cursor', cursor);
218
+ context.set('cursor', cursor); // if != 0, more remain
204
219
  const workflowResponse = await storage_1.asyncLocalStorage.run(context, async () => {
205
220
  return await workflowFunction.apply(this, workflowInput.arguments);
206
221
  });
@@ -212,11 +227,16 @@ class WorkerService {
212
227
  };
213
228
  }
214
229
  catch (err) {
230
+ if (isProcessing) {
231
+ return;
232
+ }
215
233
  if (err instanceof errors_1.DurableWaitForError || interruptionRegistry.length > 1) {
234
+ isProcessing = true;
235
+ //NOTE: this type is spawned when `Promise.all` is used OR if the interruption is a `waitFor`
216
236
  const workflowInput = data.data;
217
237
  const execIndex = counter.counter - interruptionRegistry.length + 1;
218
238
  const { workflowId, workflowTopic, workflowDimension, originJobId } = workflowInput;
219
- const collatorFlowId = `-${workflowId}-$${workflowDimension || ''}-$${execIndex}`;
239
+ const collatorFlowId = `${(0, utils_1.guid)()}$C`;
220
240
  return {
221
241
  status: stream_1.StreamStatus.SUCCESS,
222
242
  code: enums_1.HMSH_CODE_DURABLE_ALL,
@@ -235,6 +255,8 @@ class WorkerService {
235
255
  };
236
256
  }
237
257
  else if (err instanceof errors_1.DurableSleepError) {
258
+ //return the sleep interruption
259
+ isProcessing = true;
238
260
  return {
239
261
  status: stream_1.StreamStatus.SUCCESS,
240
262
  code: err.code,
@@ -249,6 +271,8 @@ class WorkerService {
249
271
  };
250
272
  }
251
273
  else if (err instanceof errors_1.DurableProxyError) {
274
+ //return the proxyActivity interruption
275
+ isProcessing = true;
252
276
  return {
253
277
  status: stream_1.StreamStatus.SUCCESS,
254
278
  code: err.code,
@@ -271,10 +295,12 @@ class WorkerService {
271
295
  };
272
296
  }
273
297
  else if (err instanceof errors_1.DurableChildError) {
298
+ //return the child interruption
299
+ isProcessing = true;
274
300
  const msg = {
275
301
  message: err.message,
276
302
  workflowId: err.workflowId,
277
- dimension: err.workflowDimension
303
+ dimension: err.workflowDimension,
278
304
  };
279
305
  return {
280
306
  status: stream_1.StreamStatus.SUCCESS,
@@ -297,6 +323,9 @@ class WorkerService {
297
323
  }
298
324
  };
299
325
  }
326
+ // ALL other errors are actual fatal errors (598, 597, 596)
327
+ // OR will be retried (599)
328
+ isProcessing = true;
300
329
  return {
301
330
  status: stream_1.StreamStatus.SUCCESS,
302
331
  code: err.code || new errors_1.DurableRetryError(err.message).code,
@@ -321,7 +350,7 @@ class WorkerService {
321
350
  }
322
351
  }
323
352
  _a = WorkerService;
324
- WorkerService.activityRegistry = {};
353
+ WorkerService.activityRegistry = {}; //user's activities
325
354
  WorkerService.instances = new Map();
326
355
  WorkerService.getHotMesh = async (workflowTopic, config, options) => {
327
356
  if (WorkerService.instances.has(workflowTopic)) {