@hotmeshio/hotmesh 0.0.55 → 0.0.56

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 (181) hide show
  1. package/build/modules/enums.js +1 -10
  2. package/build/modules/key.d.ts +0 -38
  3. package/build/modules/key.js +4 -46
  4. package/build/modules/utils.d.ts +0 -8
  5. package/build/modules/utils.js +0 -14
  6. package/build/package.json +11 -4
  7. package/build/services/activities/activity.d.ts +0 -28
  8. package/build/services/activities/activity.js +1 -46
  9. package/build/services/activities/await.js +0 -4
  10. package/build/services/activities/cycle.d.ts +0 -7
  11. package/build/services/activities/cycle.js +1 -16
  12. package/build/services/activities/hook.d.ts +0 -6
  13. package/build/services/activities/hook.js +2 -12
  14. package/build/services/activities/interrupt.js +0 -8
  15. package/build/services/activities/signal.d.ts +0 -6
  16. package/build/services/activities/signal.js +0 -15
  17. package/build/services/activities/trigger.d.ts +0 -4
  18. package/build/services/activities/trigger.js +1 -7
  19. package/build/services/activities/worker.js +0 -4
  20. package/build/services/collator/index.d.ts +0 -70
  21. package/build/services/collator/index.js +1 -91
  22. package/build/services/compiler/deployer.js +6 -38
  23. package/build/services/compiler/index.d.ts +0 -15
  24. package/build/services/compiler/index.js +0 -20
  25. package/build/services/compiler/validator.d.ts +0 -3
  26. package/build/services/compiler/validator.js +0 -25
  27. package/build/services/connector/clients/ioredis.d.ts +2 -2
  28. package/build/services/connector/clients/ioredis.js +0 -2
  29. package/build/services/connector/clients/redis.d.ts +4 -4
  30. package/build/services/connector/clients/redis.js +1 -3
  31. package/build/services/connector/index.d.ts +1 -1
  32. package/build/services/connector/index.js +0 -2
  33. package/build/services/durable/client.d.ts +1 -26
  34. package/build/services/durable/client.js +0 -56
  35. package/build/services/durable/exporter.d.ts +0 -22
  36. package/build/services/durable/exporter.js +1 -30
  37. package/build/services/durable/handle.d.ts +0 -36
  38. package/build/services/durable/handle.js +0 -46
  39. package/build/services/durable/index.d.ts +0 -4
  40. package/build/services/durable/index.js +0 -4
  41. package/build/services/durable/schemas/factory.d.ts +0 -29
  42. package/build/services/durable/schemas/factory.js +0 -29
  43. package/build/services/durable/search.d.ts +1 -36
  44. package/build/services/durable/search.js +57 -56
  45. package/build/services/durable/worker.js +2 -22
  46. package/build/services/durable/workflow.d.ts +0 -114
  47. package/build/services/durable/workflow.js +1 -141
  48. package/build/services/engine/index.d.ts +1 -6
  49. package/build/services/engine/index.js +1 -43
  50. package/build/services/exporter/index.d.ts +0 -27
  51. package/build/services/exporter/index.js +0 -33
  52. package/build/services/hotmesh/index.d.ts +2 -2
  53. package/build/services/hotmesh/index.js +1 -9
  54. package/build/services/logger/index.js +0 -2
  55. package/build/services/mapper/index.d.ts +0 -14
  56. package/build/services/mapper/index.js +0 -14
  57. package/build/services/pipe/functions/date.d.ts +0 -7
  58. package/build/services/pipe/functions/date.js +0 -7
  59. package/build/services/pipe/functions/math.js +0 -2
  60. package/build/services/pipe/index.d.ts +0 -15
  61. package/build/services/pipe/index.js +2 -23
  62. package/build/services/quorum/index.d.ts +0 -7
  63. package/build/services/quorum/index.js +0 -21
  64. package/build/services/reporter/index.d.ts +0 -5
  65. package/build/services/reporter/index.js +0 -9
  66. package/build/services/router/index.d.ts +0 -9
  67. package/build/services/router/index.js +2 -38
  68. package/build/services/serializer/index.js +7 -26
  69. package/build/services/store/cache.d.ts +0 -18
  70. package/build/services/store/cache.js +0 -18
  71. package/build/services/store/clients/ioredis.d.ts +1 -1
  72. package/build/services/store/clients/ioredis.js +0 -1
  73. package/build/services/store/clients/redis.d.ts +1 -1
  74. package/build/services/store/index.d.ts +0 -55
  75. package/build/services/store/index.js +5 -81
  76. package/build/services/stream/clients/ioredis.d.ts +1 -1
  77. package/build/services/stream/clients/ioredis.js +1 -4
  78. package/build/services/stream/clients/redis.d.ts +1 -1
  79. package/build/services/sub/clients/ioredis.d.ts +1 -1
  80. package/build/services/sub/clients/redis.d.ts +1 -1
  81. package/build/services/task/index.d.ts +0 -9
  82. package/build/services/task/index.js +0 -31
  83. package/build/services/telemetry/index.d.ts +0 -7
  84. package/build/services/telemetry/index.js +1 -13
  85. package/build/services/worker/index.d.ts +0 -4
  86. package/build/services/worker/index.js +2 -6
  87. package/build/types/activity.d.ts +0 -81
  88. package/build/types/durable.d.ts +25 -177
  89. package/build/types/exporter.d.ts +0 -13
  90. package/build/types/hotmesh.d.ts +4 -16
  91. package/build/types/hotmesh.js +0 -3
  92. package/build/types/index.d.ts +4 -6
  93. package/build/types/index.js +4 -3
  94. package/build/types/job.d.ts +1 -86
  95. package/build/types/pipe.d.ts +0 -65
  96. package/build/types/quorum.d.ts +15 -10
  97. package/build/types/redis.d.ts +225 -7
  98. package/build/types/redis.js +9 -0
  99. package/build/types/stream.d.ts +0 -58
  100. package/build/types/stream.js +0 -4
  101. package/package.json +11 -4
  102. package/types/durable.ts +121 -3
  103. package/types/hotmesh.ts +3 -6
  104. package/types/index.ts +23 -10
  105. package/types/job.ts +1 -1
  106. package/types/quorum.ts +22 -0
  107. package/types/redis.ts +267 -18
  108. package/build/types/ioredisclient.d.ts +0 -5
  109. package/build/types/ioredisclient.js +0 -5
  110. package/build/types/redisclient.d.ts +0 -26
  111. package/build/types/redisclient.js +0 -2
  112. package/modules/enums.ts +0 -62
  113. package/modules/errors.ts +0 -280
  114. package/modules/key.ts +0 -101
  115. package/modules/storage.ts +0 -3
  116. package/modules/utils.ts +0 -242
  117. package/services/activities/activity.ts +0 -589
  118. package/services/activities/await.ts +0 -113
  119. package/services/activities/cycle.ts +0 -115
  120. package/services/activities/hook.ts +0 -197
  121. package/services/activities/index.ts +0 -19
  122. package/services/activities/interrupt.ts +0 -172
  123. package/services/activities/signal.ts +0 -148
  124. package/services/activities/trigger.ts +0 -295
  125. package/services/activities/worker.ts +0 -107
  126. package/services/collator/README.md +0 -102
  127. package/services/collator/index.ts +0 -291
  128. package/services/compiler/deployer.ts +0 -504
  129. package/services/compiler/index.ts +0 -98
  130. package/services/compiler/validator.ts +0 -158
  131. package/services/connector/clients/ioredis.ts +0 -57
  132. package/services/connector/clients/redis.ts +0 -72
  133. package/services/connector/index.ts +0 -42
  134. package/services/durable/client.ts +0 -266
  135. package/services/durable/connection.ts +0 -10
  136. package/services/durable/exporter.ts +0 -232
  137. package/services/durable/handle.ts +0 -160
  138. package/services/durable/index.ts +0 -27
  139. package/services/durable/schemas/factory.ts +0 -2358
  140. package/services/durable/search.ts +0 -196
  141. package/services/durable/worker.ts +0 -401
  142. package/services/durable/workflow.ts +0 -557
  143. package/services/engine/index.ts +0 -761
  144. package/services/exporter/index.ts +0 -146
  145. package/services/hotmesh/index.ts +0 -237
  146. package/services/logger/index.ts +0 -79
  147. package/services/mapper/index.ts +0 -89
  148. package/services/pipe/functions/array.ts +0 -78
  149. package/services/pipe/functions/bitwise.ts +0 -27
  150. package/services/pipe/functions/conditional.ts +0 -35
  151. package/services/pipe/functions/date.ts +0 -220
  152. package/services/pipe/functions/index.ts +0 -27
  153. package/services/pipe/functions/json.ts +0 -11
  154. package/services/pipe/functions/logical.ts +0 -11
  155. package/services/pipe/functions/math.ts +0 -217
  156. package/services/pipe/functions/number.ts +0 -75
  157. package/services/pipe/functions/object.ts +0 -98
  158. package/services/pipe/functions/string.ts +0 -86
  159. package/services/pipe/functions/symbol.ts +0 -39
  160. package/services/pipe/functions/unary.ts +0 -19
  161. package/services/pipe/index.ts +0 -216
  162. package/services/quorum/index.ts +0 -319
  163. package/services/reporter/index.ts +0 -387
  164. package/services/router/index.ts +0 -426
  165. package/services/serializer/README.md +0 -10
  166. package/services/serializer/index.ts +0 -285
  167. package/services/store/cache.ts +0 -172
  168. package/services/store/clients/ioredis.ts +0 -145
  169. package/services/store/clients/redis.ts +0 -191
  170. package/services/store/index.ts +0 -1091
  171. package/services/stream/clients/ioredis.ts +0 -157
  172. package/services/stream/clients/redis.ts +0 -158
  173. package/services/stream/index.ts +0 -58
  174. package/services/sub/clients/ioredis.ts +0 -83
  175. package/services/sub/clients/redis.ts +0 -74
  176. package/services/sub/index.ts +0 -25
  177. package/services/task/index.ts +0 -250
  178. package/services/telemetry/index.ts +0 -273
  179. package/services/worker/index.ts +0 -248
  180. package/types/ioredisclient.ts +0 -10
  181. package/types/redisclient.ts +0 -30
@@ -1,196 +0,0 @@
1
- import { HotMeshService as HotMesh } from '../hotmesh'
2
- import { RedisClient, RedisMulti } from '../../types/redis';
3
- import { StoreService } from '../store';
4
- import { KeyService, KeyType } from '../../modules/key';
5
- import { WorkflowSearchOptions } from '../../types/durable';
6
- import { asyncLocalStorage } from '../../modules/storage';
7
-
8
- export class Search {
9
- jobId: string;
10
- searchSessionId: string;
11
- searchSessionIndex: number = 0;
12
- hotMeshClient: HotMesh;
13
- store: StoreService<RedisClient, RedisMulti> | null;
14
-
15
- constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string) {
16
- const keyParams = {
17
- appId: hotMeshClient.appId,
18
- jobId: workflowId
19
- }
20
- this.jobId = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
21
- this.searchSessionId = searchSessionId;
22
- this.hotMeshClient = hotMeshClient;
23
- this.store = hotMeshClient.engine.store as StoreService<RedisClient, RedisMulti>;
24
- }
25
-
26
- safeKey(key:string): string {
27
- return `_${key}`;
28
- }
29
-
30
- /**
31
- * For those deployments with a redis stack backend (with the FT module),
32
- * this method will configure the search index for the workflow. For all
33
- * others, this method will exit/fail gracefully and not index
34
- * the fields in the HASH. However, all values are still available
35
- * in the HASH.
36
- */
37
- static async configureSearchIndex(hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void> {
38
- if (search?.schema) {
39
- const store = hotMeshClient.engine.store;
40
- const schema: string[] = [];
41
- for (const [key, value] of Object.entries(search.schema)) {
42
- //prefix with a comma (avoids collisions with hotmesh reserved words)
43
- schema.push(`_${key}`);
44
- schema.push(value.type);
45
- if (value.sortable) {
46
- schema.push('SORTABLE');
47
- }
48
- }
49
- try {
50
- const keyParams = {
51
- appId: hotMeshClient.appId,
52
- jobId: ''
53
- }
54
- const hotMeshPrefix = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
55
- const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
56
- await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length.toString(), ...prefixes, 'SCHEMA', ...schema);
57
- } catch (error) {
58
- hotMeshClient.engine.logger.info('durable-client-search-err', { ...error });
59
- }
60
- }
61
- }
62
-
63
- /**
64
- * For those deployments with a redis stack backend (with the FT module),
65
- * this method will list all search indexes.
66
- * @param {HotMesh} hotMeshClient - the hotmesh client
67
- * @returns {Promise<string[]>} - the list of search indexes
68
- */
69
- static async listSearchIndexes(hotMeshClient: HotMesh): Promise<string[]> {
70
- try {
71
- const store = hotMeshClient.engine.store;
72
- const searchIndexes = await store.exec('FT._LIST');
73
- return searchIndexes as string[];
74
- } catch (error) {
75
- hotMeshClient.engine.logger.info('durable-client-search-list-err', { ...error });
76
- return [];
77
- }
78
- }
79
-
80
- /**
81
- * increments the index to return a unique search session guid when
82
- * calling any method that produces side effects (changes the value)
83
- */
84
- getSearchSessionGuid(): string {
85
- //return the search session as it would exist in the search session index
86
- return `${this.searchSessionId}-${this.searchSessionIndex++}-`;
87
- }
88
-
89
- /**
90
- * Sets the fields listed in args. Returns the
91
- * count of new fields that were set (does not
92
- * count fields that were updated)
93
- */
94
- async set(...args: string[]): Promise<number> {
95
- const ssGuid = this.getSearchSessionGuid();
96
- const store = asyncLocalStorage.getStore();
97
- const replay = store?.get('replay') ?? {};
98
- if (ssGuid in replay) {
99
- return Number(replay[ssGuid]);
100
- }
101
- const safeArgs: string[] = [];
102
- for (let i = 0; i < args.length; i += 2) {
103
- const key = this.safeKey(args[i]);
104
- const value = args[i+1].toString();
105
- safeArgs.push(key, value);
106
- }
107
- const fieldCount = await this.store.exec('HSET', this.jobId, ...safeArgs);
108
- //no need to wait; set this interim value in the replay
109
- this.store.exec('HSET', this.jobId, ssGuid, fieldCount.toString());
110
- return Number(fieldCount);
111
- }
112
-
113
- async get(key: string): Promise<string> {
114
- try {
115
- return await this.store.exec('HGET',this.jobId, this.safeKey(key)) as string;
116
- } catch (error) {
117
- this.hotMeshClient.logger.error('durable-search-get-error', { ...error });
118
- return '';
119
- }
120
- }
121
-
122
- async mget(...args: string[]): Promise<string[]> {
123
- const safeArgs: string[] = [];
124
- for (let i = 0; i < args.length; i++) {
125
- safeArgs.push(this.safeKey(args[i]));
126
- }
127
- try {
128
- return await this.store.exec('HMGET', this.jobId, ...safeArgs) as string[];
129
- } catch (error) {
130
- this.hotMeshClient.logger.error('durable-search-mget-error', { ...error });
131
- return [];
132
- }
133
- }
134
-
135
- /**
136
- * Deletes the fields listed in args. Returns the
137
- * count of fields that were deleted.
138
- */
139
- async del(...args: string[]): Promise<number | void> {
140
- const ssGuid = this.getSearchSessionGuid();
141
- const store = asyncLocalStorage.getStore();
142
- const replay = store?.get('replay') ?? {};
143
- if (ssGuid in replay) {
144
- return Number(replay[ssGuid]);
145
- }
146
- const safeArgs: string[] = [];
147
- for (let i = 0; i < args.length; i++) {
148
- safeArgs.push(this.safeKey(args[i]));
149
- }
150
- const response = await this.store.exec('HDEL', this.jobId, ...safeArgs);
151
- const formattedResponse = isNaN(response as unknown as number) ? 0 : Number(response);
152
- //no need to wait; set this interim value in the replay
153
- this.store.exec('HSET', this.jobId, ssGuid, formattedResponse.toString());
154
- return formattedResponse;
155
- }
156
-
157
- /**
158
- * Increments the value of a field by the given amount. Returns the
159
- * new value of the field after the increment. Can be
160
- * used to decrement the value of a field by specifying a negative.
161
- */
162
- async incr(key: string, val: number): Promise<number> {
163
- const ssGuid = this.getSearchSessionGuid();
164
- const store = asyncLocalStorage.getStore();
165
- const replay = store?.get('replay') ?? {};
166
- if (ssGuid in replay) {
167
- return Number(replay[ssGuid]);
168
- }
169
- const num = await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), val.toString()) as string;
170
- //no need to wait; set this interim value in the replay
171
- this.store.exec('HSET', this.jobId, ssGuid, num.toString());
172
- return Number(num);
173
- }
174
-
175
- /**
176
- * Multiplies the value of a field by the given amount. Returns the
177
- * new value of the field after the multiplication. NOTE:
178
- * this is exponential multiplication.
179
- */
180
- async mult(key: string, val: number): Promise<number> {
181
- const ssGuid = this.getSearchSessionGuid();
182
- const store = asyncLocalStorage.getStore();
183
- const replay = store?.get('replay') ?? {};
184
- if (ssGuid in replay) {
185
- return Math.exp(Number(replay[ssGuid]));
186
- }
187
- const ssGuidValue = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, ssGuid, '1') as string);
188
- if (ssGuidValue === 1) {
189
- const log = Math.log(val);
190
- const logTotal = await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), log.toString()) as string;
191
- //no need to wait; set this interim value in the replay
192
- this.store.exec('HSET', this.jobId, ssGuid, logTotal.toString());
193
- return Math.exp(Number(logTotal));
194
- }
195
- }
196
- }
@@ -1,401 +0,0 @@
1
- import ms from 'ms';
2
- import {
3
- HMSH_CODE_DURABLE_ALL,
4
- HMSH_CODE_DURABLE_RETRYABLE,
5
- HMSH_DURABLE_EXP_BACKOFF,
6
- HMSH_DURABLE_MAX_INTERVAL,
7
- HMSH_DURABLE_MAX_ATTEMPTS,
8
- HMSH_LOGLEVEL } from '../../modules/enums';
9
- import {
10
- DurableChildError,
11
- DurableFatalError,
12
- DurableMaxedError,
13
- DurableProxyError,
14
- DurableRetryError,
15
- DurableSleepError,
16
- DurableTimeoutError,
17
- DurableWaitForError } from '../../modules/errors';
18
- import { asyncLocalStorage } from '../../modules/storage';
19
- import { APP_ID, APP_VERSION, getWorkflowYAML } from './schemas/factory';
20
- import { HotMeshService as HotMesh } from '../hotmesh';
21
- import {
22
- ActivityWorkflowDataType,
23
- Connection,
24
- Registry,
25
- WorkerConfig,
26
- WorkerOptions,
27
- WorkflowDataType } from '../../types/durable';
28
- import { RedisClass, RedisOptions } from '../../types/redis';
29
- import { Search } from './search';
30
- import {
31
- StreamData,
32
- StreamDataResponse,
33
- StreamStatus } from '../../types/stream';
34
- import { formatISODate } from '../../modules/utils';
35
-
36
- export class WorkerService {
37
- static activityRegistry: Registry = {}; //user's activities
38
- static connection: Connection;
39
- static instances = new Map<string, HotMesh | Promise<HotMesh>>();
40
- workflowRunner: HotMesh;
41
- activityRunner: HotMesh;
42
-
43
- static getHotMesh = async (workflowTopic: string, config?: Partial<WorkerConfig>, options?: WorkerOptions) => {
44
- if (WorkerService.instances.has(workflowTopic)) {
45
- return await WorkerService.instances.get(workflowTopic);
46
- }
47
- const hotMeshClient = HotMesh.init({
48
- logLevel: options?.logLevel ?? HMSH_LOGLEVEL,
49
- appId: config.namespace ?? APP_ID,
50
- engine: { redis: { ...WorkerService.connection } }
51
- });
52
- WorkerService.instances.set(workflowTopic, hotMeshClient);
53
- await WorkerService.activateWorkflow(await hotMeshClient);
54
- return hotMeshClient;
55
- }
56
-
57
- static async activateWorkflow(hotMesh: HotMesh) {
58
- const app = await hotMesh.engine.store.getApp(hotMesh.engine.appId);
59
- const appVersion = app?.version;
60
- if(!appVersion) {
61
- try {
62
- await hotMesh.deploy(getWorkflowYAML(hotMesh.engine.appId, APP_VERSION));
63
- await hotMesh.activate(APP_VERSION);
64
- } catch (err) {
65
- hotMesh.engine.logger.error('durable-worker-deploy-activate-err', err);
66
- throw err;
67
- }
68
- } else if(app && !app.active) {
69
- try {
70
- await hotMesh.activate(APP_VERSION);
71
- } catch (err) {
72
- hotMesh.engine.logger.error('durable-worker-activate-err', err);
73
- throw err;
74
- }
75
- }
76
- }
77
-
78
- static registerActivities<ACT>(activities: ACT): Registry {
79
- if (typeof activities === 'function' && typeof WorkerService.activityRegistry[activities.name] !== 'function') {
80
- WorkerService.activityRegistry[activities.name] = activities as Function;
81
- } else {
82
- Object.keys(activities).forEach(key => {
83
- if (activities[key].name && typeof WorkerService.activityRegistry[activities[key].name] !== 'function') {
84
- WorkerService.activityRegistry[activities[key].name] = (activities as any)[key] as Function;
85
- } else if (typeof (activities as any)[key] === 'function') {
86
- WorkerService.activityRegistry[key] = (activities as any)[key] as Function;
87
- }
88
- });
89
- }
90
- return WorkerService.activityRegistry;
91
- }
92
-
93
- static async create(config: WorkerConfig): Promise<WorkerService> {
94
- WorkerService.connection = config.connection;
95
- const workflow = config.workflow;
96
- const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
97
- const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
98
- const activityTopic = `${baseTopic}-activity`;
99
- const workflowTopic = `${baseTopic}`;
100
-
101
- //initialize supporting workflows
102
- const worker = new WorkerService();
103
- worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
104
- worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
105
- Search.configureSearchIndex(worker.workflowRunner, config.search);
106
- await WorkerService.activateWorkflow(worker.workflowRunner);
107
- return worker;
108
- }
109
-
110
- static resolveWorkflowTarget(workflow: object | Function, name?: string): [string, Function] {
111
- let workflowFunction: Function;
112
- if (typeof workflow === 'function') {
113
- workflowFunction = workflow;
114
- return [workflowFunction.name ?? name, workflowFunction];
115
- } else {
116
- const workflowFunctionNames = Object.keys(workflow);
117
- const lastFunctionName = workflowFunctionNames[workflowFunctionNames.length - 1];
118
- workflowFunction = workflow[lastFunctionName];
119
- return WorkerService.resolveWorkflowTarget(workflowFunction, lastFunctionName);
120
- }
121
- }
122
-
123
- async run() {
124
- this.workflowRunner.engine.logger.info('WorkerService is running');
125
- }
126
-
127
- async initActivityWorker(config: WorkerConfig, activityTopic: string): Promise<HotMesh> {
128
- const redisConfig = {
129
- class: config.connection.class as RedisClass,
130
- options: config.connection.options as RedisOptions
131
- };
132
- const hotMeshWorker = await HotMesh.init({
133
- logLevel: config.options?.logLevel ?? HMSH_LOGLEVEL,
134
- appId: config.namespace ?? APP_ID,
135
- engine: { redis: redisConfig },
136
- workers: [
137
- { topic: activityTopic,
138
- redis: redisConfig,
139
- callback: this.wrapActivityFunctions().bind(this)
140
- }
141
- ]
142
- });
143
- WorkerService.instances.set(activityTopic, hotMeshWorker);
144
- return hotMeshWorker;
145
- }
146
-
147
- //this is the linked worker function in the reentrant workflow test
148
- wrapActivityFunctions(): Function {
149
- return async (data: StreamData): Promise<StreamDataResponse> => {
150
- try {
151
- //always run the activity function when instructed; return the response
152
- const activityInput = data.data as unknown as ActivityWorkflowDataType;
153
- const activityName = activityInput.activityName;
154
- const activityFunction = WorkerService.activityRegistry[activityName];
155
- const pojoResponse = await activityFunction.apply(this, activityInput.arguments);
156
-
157
- return {
158
- status: StreamStatus.SUCCESS,
159
- metadata: { ...data.metadata },
160
- data: { response: pojoResponse }
161
- };
162
- } catch (err) {
163
- this.activityRunner.engine.logger.error('durable-worker-activity-err', { name: err.name, message: err.message, stack: err.stack });
164
- if (!(err instanceof DurableTimeoutError) &&
165
- !(err instanceof DurableMaxedError) &&
166
- !(err instanceof DurableFatalError)) {
167
-
168
- //use code 599 as a proxy for all retryable errors
169
- // (basically anything not 596, 597, 598)
170
- return {
171
- status: StreamStatus.SUCCESS,
172
- code: HMSH_CODE_DURABLE_RETRYABLE,
173
- metadata: { ...data.metadata },
174
- data: {
175
- $error: {
176
- message: err.message,
177
- stack: err.stack,
178
- timestamp: formatISODate(new Date()),
179
- }
180
- },
181
- } as StreamDataResponse;
182
- }
183
-
184
- return {
185
- //always returrn success (the Durable module is just fine);
186
- // it's the user's function that has failed
187
- status: StreamStatus.SUCCESS,
188
- code: err.code,
189
- stack: err.stack,
190
- metadata: { ...data.metadata },
191
- data: {
192
- $error: {
193
- message: err.message,
194
- stack: err.stack,
195
- timestamp: formatISODate(new Date()),
196
- code: err.code,
197
- }
198
- },
199
- } as StreamDataResponse;
200
- }
201
- }
202
- }
203
-
204
- async initWorkflowWorker(config: WorkerConfig, workflowTopic: string, workflowFunction: Function): Promise<HotMesh> {
205
- const redisConfig = {
206
- class: config.connection.class as RedisClass,
207
- options: config.connection.options as RedisOptions
208
- };
209
- const hotMeshWorker = await HotMesh.init({
210
- logLevel: config.options?.logLevel ?? HMSH_LOGLEVEL,
211
- appId: config.namespace ?? APP_ID,
212
- engine: { redis: redisConfig },
213
- workers: [{
214
- topic: workflowTopic,
215
- redis: redisConfig,
216
- callback: this.wrapWorkflowFunction(workflowFunction, workflowTopic, config).bind(this)
217
- }]
218
- });
219
- WorkerService.instances.set(workflowTopic, hotMeshWorker);
220
- return hotMeshWorker;
221
- }
222
-
223
- static Context = {
224
- info: () => {
225
- return {
226
- workflowId: '',
227
- workflowTopic: '',
228
- }
229
- },
230
- };
231
-
232
- wrapWorkflowFunction(workflowFunction: Function, workflowTopic: string, config: WorkerConfig): Function {
233
- return async (data: StreamData): Promise<StreamDataResponse> => {
234
- const counter = { counter: 0 };
235
- const interruptionRegistry: any[] = [];
236
- try {
237
- //incoming data payload has arguments and workflowId
238
- const workflowInput = data.data as unknown as WorkflowDataType;
239
- const context = new Map();
240
- context.set('canRetry', workflowInput.canRetry);
241
- context.set('counter', counter);
242
- context.set('interruptionRegistry', interruptionRegistry);
243
- context.set('namespace', config.namespace ?? APP_ID);
244
- context.set('raw', data);
245
- context.set('workflowId', workflowInput.workflowId);
246
- if (workflowInput.originJobId) {
247
- //if present there is an origin job to which this job is subordinated;
248
- // garbage collect (expire) this job when originJobId is expired
249
- context.set('originJobId', workflowInput.originJobId);
250
- }
251
- let replayQuery = '';
252
- if (workflowInput.workflowDimension) {
253
- //every hook function runs in an isolated dimension controlled
254
- //by the index assigned when the signal was received; even if the
255
- //hook function re-runs, its scope will always remain constant
256
- context.set('workflowDimension', workflowInput.workflowDimension);
257
- replayQuery = `-*${workflowInput.workflowDimension}-*`;
258
- } else {
259
- //last letter of words like 'hook', 'sleep', 'wait', 'signal', 'search', 'start', 'proxy', 'child', 'collator'
260
- replayQuery = '-*[ehklptydr]-*';
261
- }
262
- context.set('workflowTopic', workflowTopic);
263
- context.set('workflowName', workflowTopic.split('-').pop());
264
- context.set('workflowTrace', data.metadata.trc);
265
- context.set('workflowSpan', data.metadata.spn);
266
- const store = this.workflowRunner.engine.store;
267
- const [cursor, replay] = await store.findJobFields(
268
- workflowInput.workflowId,
269
- replayQuery,
270
- 50_000,
271
- 5_000,);
272
- context.set('replay', replay);
273
- context.set('cursor', cursor); // if != 0, more remain
274
- const workflowResponse = await asyncLocalStorage.run(context, async () => {
275
- return await workflowFunction.apply(this, workflowInput.arguments);
276
- });
277
-
278
- return {
279
- code: 200,
280
- status: StreamStatus.SUCCESS,
281
- metadata: { ...data.metadata },
282
- data: { response: workflowResponse, done: true }
283
- };
284
- } catch (err) {
285
- if (err instanceof DurableWaitForError || interruptionRegistry.length > 1) {
286
-
287
- //NOTE: this type is spawned when `Promise.all` is used OR if the interruption is a `waitFor`
288
- const workflowInput = data.data as unknown as WorkflowDataType;
289
- const execIndex = counter.counter - interruptionRegistry.length + 1;
290
- const { workflowId, workflowTopic, workflowDimension, originJobId } = workflowInput;
291
- const collatorFlowId = `-${workflowId}-$${workflowDimension || ''}-$${execIndex}`;
292
- return {
293
- status: StreamStatus.SUCCESS,
294
- code: HMSH_CODE_DURABLE_ALL,
295
- metadata: { ...data.metadata },
296
- data: {
297
- code: HMSH_CODE_DURABLE_ALL,
298
- items: [...interruptionRegistry],
299
- size: interruptionRegistry.length,
300
- workflowDimension: workflowDimension || '',
301
- index: execIndex,
302
- originJobId: originJobId || workflowId,
303
- parentWorkflowId: workflowId,
304
- workflowId: collatorFlowId,
305
- workflowTopic: workflowTopic,
306
- },
307
- } as StreamDataResponse;
308
-
309
- } else if (err instanceof DurableSleepError) {
310
- //return the sleep interruption
311
- return {
312
- status: StreamStatus.SUCCESS,
313
- code: err.code,
314
- metadata: { ...data.metadata },
315
- data: {
316
- code: err.code,
317
- message: JSON.stringify({ duration: err.duration, index: err.index, workflowDimension: err.workflowDimension }),
318
- duration: err.duration,
319
- index: err.index,
320
- workflowDimension: err.workflowDimension,
321
- }
322
- } as StreamDataResponse;
323
-
324
- } else if (err instanceof DurableProxyError) {
325
- //return the proxyActivity interruption
326
- return {
327
- status: StreamStatus.SUCCESS,
328
- code: err.code,
329
- metadata: { ...data.metadata },
330
- data: {
331
- code: err.code,
332
- message: JSON.stringify({ message: err.message, workflowId: err.workflowId, activityName: err.activityName, dimension: err.workflowDimension }),
333
- arguments: err.arguments,
334
- workflowDimension: err.workflowDimension,
335
- index: err.index,
336
- originJobId: err.originJobId,
337
- parentWorkflowId: err.parentWorkflowId,
338
- workflowId: err.workflowId,
339
- workflowTopic: err.workflowTopic,
340
- activityName: err.activityName,
341
- backoffCoefficient: err.backoffCoefficient,
342
- maximumAttempts: err.maximumAttempts,
343
- maximumInterval: err.maximumInterval,
344
- }
345
- } as StreamDataResponse;
346
-
347
- } else if (err instanceof DurableChildError) {
348
- //return the child interruption
349
- const msg = {
350
- message: err.message,
351
- workflowId: err.workflowId,
352
- dimension: err.workflowDimension
353
- };
354
- return {
355
- status: StreamStatus.SUCCESS,
356
- code: err.code,
357
- metadata: { ...data.metadata },
358
- data: {
359
- arguments: err.arguments,
360
- await: err.await,
361
- backoffCoefficient: err.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF ,
362
- code: err.code,
363
- index: err.index,
364
- message: JSON.stringify(msg),
365
- maximumAttempts: err.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
366
- maximumInterval: err.maximumInterval || ms(HMSH_DURABLE_MAX_INTERVAL) / 1000,
367
- originJobId: err.originJobId,
368
- parentWorkflowId: err.parentWorkflowId,
369
- workflowDimension: err.workflowDimension,
370
- workflowId: err.workflowId,
371
- workflowTopic: err.workflowTopic,
372
- }
373
- } as StreamDataResponse;
374
- }
375
-
376
- // ALL other errors are actual fatal errors (598, 597, 596)
377
- // OR will be retried (599)
378
- return {
379
- status: StreamStatus.SUCCESS,
380
- code: err.code || new DurableRetryError(err.message).code,
381
- metadata: { ...data.metadata },
382
- data: {
383
- $error: {
384
- message: err.message,
385
- type: err.name,
386
- name: err.name,
387
- stack: err.stack,
388
- code: err.code || new DurableRetryError(err.message).code,
389
- }
390
- }
391
- } as StreamDataResponse;
392
- }
393
- }
394
- }
395
-
396
- static async shutdown(): Promise<void> {
397
- for (const [_, hotMeshInstance] of WorkerService.instances) {
398
- (await hotMeshInstance).stop();
399
- }
400
- }
401
- }