@hotmeshio/hotmesh 0.0.55 → 0.0.57

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 (182) hide show
  1. package/README.md +1 -1
  2. package/build/modules/enums.js +1 -10
  3. package/build/modules/key.d.ts +0 -38
  4. package/build/modules/key.js +4 -46
  5. package/build/modules/utils.d.ts +0 -8
  6. package/build/modules/utils.js +0 -14
  7. package/build/package.json +11 -4
  8. package/build/services/activities/activity.d.ts +0 -28
  9. package/build/services/activities/activity.js +1 -46
  10. package/build/services/activities/await.js +0 -4
  11. package/build/services/activities/cycle.d.ts +0 -7
  12. package/build/services/activities/cycle.js +1 -16
  13. package/build/services/activities/hook.d.ts +0 -6
  14. package/build/services/activities/hook.js +2 -12
  15. package/build/services/activities/interrupt.js +0 -8
  16. package/build/services/activities/signal.d.ts +0 -6
  17. package/build/services/activities/signal.js +0 -15
  18. package/build/services/activities/trigger.d.ts +0 -4
  19. package/build/services/activities/trigger.js +1 -7
  20. package/build/services/activities/worker.js +0 -4
  21. package/build/services/collator/index.d.ts +0 -70
  22. package/build/services/collator/index.js +1 -91
  23. package/build/services/compiler/deployer.js +6 -38
  24. package/build/services/compiler/index.d.ts +0 -15
  25. package/build/services/compiler/index.js +0 -20
  26. package/build/services/compiler/validator.d.ts +0 -3
  27. package/build/services/compiler/validator.js +0 -25
  28. package/build/services/connector/clients/ioredis.d.ts +2 -2
  29. package/build/services/connector/clients/ioredis.js +0 -2
  30. package/build/services/connector/clients/redis.d.ts +4 -4
  31. package/build/services/connector/clients/redis.js +1 -3
  32. package/build/services/connector/index.d.ts +1 -1
  33. package/build/services/connector/index.js +0 -2
  34. package/build/services/durable/client.d.ts +1 -26
  35. package/build/services/durable/client.js +0 -56
  36. package/build/services/durable/exporter.d.ts +0 -22
  37. package/build/services/durable/exporter.js +1 -30
  38. package/build/services/durable/handle.d.ts +0 -36
  39. package/build/services/durable/handle.js +0 -46
  40. package/build/services/durable/index.d.ts +0 -4
  41. package/build/services/durable/index.js +0 -4
  42. package/build/services/durable/schemas/factory.d.ts +0 -29
  43. package/build/services/durable/schemas/factory.js +0 -29
  44. package/build/services/durable/search.d.ts +1 -36
  45. package/build/services/durable/search.js +56 -56
  46. package/build/services/durable/worker.js +2 -22
  47. package/build/services/durable/workflow.d.ts +0 -114
  48. package/build/services/durable/workflow.js +1 -141
  49. package/build/services/engine/index.d.ts +1 -6
  50. package/build/services/engine/index.js +1 -43
  51. package/build/services/exporter/index.d.ts +0 -27
  52. package/build/services/exporter/index.js +0 -33
  53. package/build/services/hotmesh/index.d.ts +2 -2
  54. package/build/services/hotmesh/index.js +1 -9
  55. package/build/services/logger/index.js +0 -2
  56. package/build/services/mapper/index.d.ts +0 -14
  57. package/build/services/mapper/index.js +0 -14
  58. package/build/services/pipe/functions/date.d.ts +0 -7
  59. package/build/services/pipe/functions/date.js +0 -7
  60. package/build/services/pipe/functions/math.js +0 -2
  61. package/build/services/pipe/index.d.ts +0 -15
  62. package/build/services/pipe/index.js +2 -23
  63. package/build/services/quorum/index.d.ts +0 -7
  64. package/build/services/quorum/index.js +0 -21
  65. package/build/services/reporter/index.d.ts +0 -5
  66. package/build/services/reporter/index.js +0 -9
  67. package/build/services/router/index.d.ts +0 -9
  68. package/build/services/router/index.js +2 -38
  69. package/build/services/serializer/index.js +7 -26
  70. package/build/services/store/cache.d.ts +0 -18
  71. package/build/services/store/cache.js +0 -18
  72. package/build/services/store/clients/ioredis.d.ts +1 -1
  73. package/build/services/store/clients/ioredis.js +0 -1
  74. package/build/services/store/clients/redis.d.ts +1 -1
  75. package/build/services/store/index.d.ts +0 -55
  76. package/build/services/store/index.js +5 -81
  77. package/build/services/stream/clients/ioredis.d.ts +1 -1
  78. package/build/services/stream/clients/ioredis.js +1 -4
  79. package/build/services/stream/clients/redis.d.ts +1 -1
  80. package/build/services/sub/clients/ioredis.d.ts +1 -1
  81. package/build/services/sub/clients/redis.d.ts +1 -1
  82. package/build/services/task/index.d.ts +0 -9
  83. package/build/services/task/index.js +0 -31
  84. package/build/services/telemetry/index.d.ts +0 -7
  85. package/build/services/telemetry/index.js +1 -13
  86. package/build/services/worker/index.d.ts +0 -4
  87. package/build/services/worker/index.js +2 -6
  88. package/build/types/activity.d.ts +0 -81
  89. package/build/types/durable.d.ts +26 -177
  90. package/build/types/exporter.d.ts +0 -13
  91. package/build/types/hotmesh.d.ts +4 -16
  92. package/build/types/hotmesh.js +0 -3
  93. package/build/types/index.d.ts +4 -6
  94. package/build/types/index.js +4 -3
  95. package/build/types/job.d.ts +1 -86
  96. package/build/types/pipe.d.ts +0 -65
  97. package/build/types/quorum.d.ts +15 -10
  98. package/build/types/redis.d.ts +225 -7
  99. package/build/types/redis.js +9 -0
  100. package/build/types/stream.d.ts +0 -58
  101. package/build/types/stream.js +0 -4
  102. package/package.json +11 -4
  103. package/types/durable.ts +131 -4
  104. package/types/hotmesh.ts +3 -6
  105. package/types/index.ts +23 -10
  106. package/types/job.ts +1 -1
  107. package/types/quorum.ts +22 -0
  108. package/types/redis.ts +267 -18
  109. package/build/types/ioredisclient.d.ts +0 -5
  110. package/build/types/ioredisclient.js +0 -5
  111. package/build/types/redisclient.d.ts +0 -26
  112. package/build/types/redisclient.js +0 -2
  113. package/modules/enums.ts +0 -62
  114. package/modules/errors.ts +0 -280
  115. package/modules/key.ts +0 -101
  116. package/modules/storage.ts +0 -3
  117. package/modules/utils.ts +0 -242
  118. package/services/activities/activity.ts +0 -589
  119. package/services/activities/await.ts +0 -113
  120. package/services/activities/cycle.ts +0 -115
  121. package/services/activities/hook.ts +0 -197
  122. package/services/activities/index.ts +0 -19
  123. package/services/activities/interrupt.ts +0 -172
  124. package/services/activities/signal.ts +0 -148
  125. package/services/activities/trigger.ts +0 -295
  126. package/services/activities/worker.ts +0 -107
  127. package/services/collator/README.md +0 -102
  128. package/services/collator/index.ts +0 -291
  129. package/services/compiler/deployer.ts +0 -504
  130. package/services/compiler/index.ts +0 -98
  131. package/services/compiler/validator.ts +0 -158
  132. package/services/connector/clients/ioredis.ts +0 -57
  133. package/services/connector/clients/redis.ts +0 -72
  134. package/services/connector/index.ts +0 -42
  135. package/services/durable/client.ts +0 -266
  136. package/services/durable/connection.ts +0 -10
  137. package/services/durable/exporter.ts +0 -232
  138. package/services/durable/handle.ts +0 -160
  139. package/services/durable/index.ts +0 -27
  140. package/services/durable/schemas/factory.ts +0 -2358
  141. package/services/durable/search.ts +0 -196
  142. package/services/durable/worker.ts +0 -401
  143. package/services/durable/workflow.ts +0 -557
  144. package/services/engine/index.ts +0 -761
  145. package/services/exporter/index.ts +0 -146
  146. package/services/hotmesh/index.ts +0 -237
  147. package/services/logger/index.ts +0 -79
  148. package/services/mapper/index.ts +0 -89
  149. package/services/pipe/functions/array.ts +0 -78
  150. package/services/pipe/functions/bitwise.ts +0 -27
  151. package/services/pipe/functions/conditional.ts +0 -35
  152. package/services/pipe/functions/date.ts +0 -220
  153. package/services/pipe/functions/index.ts +0 -27
  154. package/services/pipe/functions/json.ts +0 -11
  155. package/services/pipe/functions/logical.ts +0 -11
  156. package/services/pipe/functions/math.ts +0 -217
  157. package/services/pipe/functions/number.ts +0 -75
  158. package/services/pipe/functions/object.ts +0 -98
  159. package/services/pipe/functions/string.ts +0 -86
  160. package/services/pipe/functions/symbol.ts +0 -39
  161. package/services/pipe/functions/unary.ts +0 -19
  162. package/services/pipe/index.ts +0 -216
  163. package/services/quorum/index.ts +0 -319
  164. package/services/reporter/index.ts +0 -387
  165. package/services/router/index.ts +0 -426
  166. package/services/serializer/README.md +0 -10
  167. package/services/serializer/index.ts +0 -285
  168. package/services/store/cache.ts +0 -172
  169. package/services/store/clients/ioredis.ts +0 -145
  170. package/services/store/clients/redis.ts +0 -191
  171. package/services/store/index.ts +0 -1091
  172. package/services/stream/clients/ioredis.ts +0 -157
  173. package/services/stream/clients/redis.ts +0 -158
  174. package/services/stream/index.ts +0 -58
  175. package/services/sub/clients/ioredis.ts +0 -83
  176. package/services/sub/clients/redis.ts +0 -74
  177. package/services/sub/index.ts +0 -25
  178. package/services/task/index.ts +0 -250
  179. package/services/telemetry/index.ts +0 -273
  180. package/services/worker/index.ts +0 -248
  181. package/types/ioredisclient.ts +0 -10
  182. package/types/redisclient.ts +0 -30
@@ -1,557 +0,0 @@
1
- import ms from 'ms';
2
-
3
- import {
4
- DurableChildError,
5
- DurableFatalError,
6
- DurableMaxedError,
7
- DurableProxyError,
8
- DurableRetryError,
9
- DurableSleepError,
10
- DurableTimeoutError,
11
- DurableWaitForError } from '../../modules/errors';
12
- import { KeyService, KeyType } from '../../modules/key';
13
- import { asyncLocalStorage } from '../../modules/storage';
14
- import {
15
- deterministicRandom,
16
- formatISODate,
17
- guid,
18
- sleepFor } from '../../modules/utils';
19
- import { Search } from './search';
20
- import { WorkerService } from './worker';
21
- import { HotMeshService as HotMesh } from '../hotmesh';
22
- import { SerializerService } from '../serializer';
23
- import {
24
- ActivityConfig,
25
- ChildResponseType,
26
- HookOptions,
27
- ProxyResponseType,
28
- ProxyType,
29
- WorkflowContext,
30
- WorkflowOptions
31
- } from "../../types/durable";
32
- import { JobInterruptOptions } from '../../types/job';
33
- import { StreamCode, StreamStatus } from '../../types/stream';
34
- import { StringStringType } from '../../types/serializer';
35
- import {
36
- HMSH_CODE_DURABLE_CHILD,
37
- HMSH_CODE_DURABLE_FATAL,
38
- HMSH_CODE_DURABLE_MAXED,
39
- HMSH_CODE_DURABLE_PROXY,
40
- HMSH_CODE_DURABLE_SLEEP,
41
- HMSH_CODE_DURABLE_TIMEOUT,
42
- HMSH_CODE_DURABLE_WAIT,
43
- HMSH_DURABLE_EXP_BACKOFF,
44
- HMSH_DURABLE_MAX_ATTEMPTS,
45
- HMSH_DURABLE_MAX_INTERVAL} from '../../modules/enums';
46
- import { DurableChildErrorType, DurableProxyErrorType } from '../../types/error';
47
-
48
- export class WorkflowService {
49
-
50
- /**
51
- * Returns the synchronous output from the activity (replay)
52
- * if available locally, revealing whether or not the activity already
53
- * ran during a prior execution cycle
54
- * @param {string} prefix - one of: proxy, child, start, wait etc
55
- * @returns
56
- */
57
- static async didRun(prefix: string): Promise<[boolean, number, any]> {
58
- const {
59
- COUNTER,
60
- replay,
61
- workflowDimension,
62
- } = WorkflowService.getContext();
63
- const execIndex = COUNTER.counter = COUNTER.counter + 1;
64
- const sessionId = `-${prefix}${workflowDimension}-${execIndex}-`;
65
- if (sessionId in replay) {
66
- const restored = SerializerService.fromString(replay[sessionId]);
67
- return [true, execIndex, restored];
68
- }
69
- return [false, execIndex, null];
70
- }
71
-
72
- /**
73
- * Those methods that may only be called once must be protected by flagging
74
- * their execution with a unique key (the key is stored in the HASH alongside
75
- * process state and job state)
76
- * @private
77
- */
78
- static async isSideEffectAllowed(hotMeshClient: HotMesh, prefix: string): Promise<boolean> {
79
- const store = asyncLocalStorage.getStore();
80
- const workflowId = store.get('workflowId');
81
- const workflowDimension = store.get('workflowDimension') ?? '';
82
- const COUNTER = store.get('counter');
83
- const execIndex = COUNTER.counter = COUNTER.counter + 1;
84
- const sessionId = `-${prefix}${workflowDimension}-${execIndex}-`;
85
- const replay = store.get('replay') as StringStringType;
86
- if (sessionId in replay) {
87
- return false;
88
- }
89
- const keyParams = {
90
- appId: hotMeshClient.appId,
91
- jobId: workflowId
92
- }
93
- const workflowGuid = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
94
- const guidValue = Number(
95
- await hotMeshClient.engine.store.exec(
96
- 'HINCRBYFLOAT',
97
- workflowGuid,
98
- sessionId,
99
- '1') as string
100
- );
101
- return guidValue === 1;
102
- }
103
-
104
- /**
105
- * Returns the current workflow context restored
106
- * from Redis
107
- */
108
- static getContext(): WorkflowContext {
109
- const store = asyncLocalStorage.getStore();
110
- const workflowId = store.get('workflowId');
111
- const replay = store.get('replay');
112
- const cursor = store.get('cursor');
113
- const interruptionRegistry = store.get('interruptionRegistry');
114
- const workflowDimension = store.get('workflowDimension') ?? '';
115
- const workflowTopic = store.get('workflowTopic');
116
- const namespace = store.get('namespace');
117
- const originJobId = store.get('originJobId');
118
- const workflowTrace = store.get('workflowTrace');
119
- const canRetry = store.get('canRetry');
120
- const workflowSpan = store.get('workflowSpan');
121
- const COUNTER = store.get('counter');
122
- const raw = store.get('raw');
123
- return {
124
- canRetry,
125
- COUNTER,
126
- counter: COUNTER.counter,
127
- cursor,
128
- interruptionRegistry,
129
- namespace,
130
- originJobId,
131
- raw,
132
- replay,
133
- workflowId,
134
- workflowDimension,
135
- workflowTopic,
136
- workflowTrace,
137
- workflowSpan,
138
- };
139
- }
140
-
141
- /**
142
- * Return a handle to the hotmesh client hosting the workflow execution
143
- * @returns {Promise<HotMesh>} - a hotmesh client
144
- */
145
- static async getHotMesh(): Promise<HotMesh> {
146
- const store = asyncLocalStorage.getStore();
147
- const workflowTopic = store.get('workflowTopic');
148
- const namespace = store.get('namespace');
149
- return await WorkerService.getHotMesh(workflowTopic, { namespace });
150
- }
151
-
152
- /**
153
- * Spawns a child workflow and awaits the return.
154
- * @template T - the result type
155
- * @param {WorkflowOptions} options - the workflow options
156
- * @returns {Promise<T>} - the result of the child workflow
157
- * @example
158
- * const result = await Durable.workflow.execChild<typeof resultType>({ ...options });
159
- */
160
- static async execChild<T>(options: WorkflowOptions): Promise<T> {
161
- //SYNC
162
- //check if the activity already ran (check $error/done)
163
- const isStartChild = options.await === false;
164
- const prefix = isStartChild ? 'start' : 'child';
165
- const [didRun, execIndex, result]: [boolean, number, ChildResponseType<T>] = await WorkflowService.didRun(prefix);
166
- const context = WorkflowService.getContext();
167
- const { canRetry, interruptionRegistry } = context;
168
-
169
- if (didRun) {
170
- if (result?.$error && (!result.$error.is_stream_error || (result.$error.is_stream_error && !canRetry))) {
171
- if (options?.config?.throwOnError !== false) {
172
- //rethrow remote execution error (simulates local failure)
173
- const code: StreamCode = result.$error.code;
174
- const message = result.$error.message;
175
- const stack = result.$error.stack;
176
- if (code === HMSH_CODE_DURABLE_FATAL) {
177
- throw new DurableFatalError(message, stack);
178
- } else if (code == HMSH_CODE_DURABLE_MAXED) {
179
- throw new DurableMaxedError(message, stack);
180
- } else if (code == HMSH_CODE_DURABLE_TIMEOUT) {
181
- throw new DurableTimeoutError(message, stack);
182
- } else {
183
- throw new DurableRetryError(message, stack);
184
- }
185
- }
186
- return result.$error as T;
187
- } else if (result.data) {
188
- return result.data as T;
189
- }
190
- }
191
- const interruptionMessage = WorkflowService.getChildInterruptPayload(context, options, execIndex);
192
- //push the packaged inputs to the registry
193
- interruptionRegistry.push({
194
- code: HMSH_CODE_DURABLE_CHILD,
195
- ...interruptionMessage,
196
- });
197
- //ASYNC
198
- //sleep (allow others to be packaged / registered) and throw the error
199
- await sleepFor(0);
200
- throw new DurableChildError(interruptionMessage );
201
- }
202
-
203
- /**
204
- * constructs the payload necessary to spawn a child job
205
- * @private
206
- */
207
- static getChildInterruptPayload(context: WorkflowContext, options: WorkflowOptions, execIndex: number): DurableChildErrorType {
208
- const { workflowId, originJobId, workflowDimension } = context; let childJobId: string;
209
- if (options.workflowId) {
210
- childJobId = options.workflowId;
211
- } else if (options.entity) {
212
- childJobId = `${options.entity}-${workflowId.substring(0, 7)}-${guid()}-${workflowDimension}-${execIndex}`;
213
- } else {
214
- childJobId = `-${options.workflowName}-${guid()}-${workflowDimension}-${execIndex}`;
215
- }
216
- const parentWorkflowId = workflowId;
217
- const taskQueueName = options.entity ?? options.taskQueue;
218
- const workflowName = options.entity ?? options.workflowName;
219
- const workflowTopic = `${taskQueueName}-${workflowName}`;
220
- return {
221
- arguments: [...(options.args || [])],
222
- await: options?.await ?? true,
223
- backoffCoefficient: options?.config?.backoffCoefficient ?? HMSH_DURABLE_EXP_BACKOFF,
224
- index: execIndex,
225
- maximumAttempts: options?.config?.maximumAttempts ?? HMSH_DURABLE_MAX_ATTEMPTS,
226
- maximumInterval: ms(options?.config?.maximumInterval ?? HMSH_DURABLE_MAX_INTERVAL) / 1000,
227
- originJobId: originJobId ?? workflowId,
228
- parentWorkflowId,
229
- workflowDimension: workflowDimension,
230
- workflowId: childJobId,
231
- workflowTopic,
232
- };
233
- }
234
-
235
- /**
236
- * Spawns a child workflow and returns the child Job ID.
237
- * This method guarantees the spawned child has reserved the Job ID,
238
- * returning a 'DuplicateJobError' error if not. Otherwise,
239
- * this is a fire-and-forget method.
240
- *
241
- * @param {WorkflowOptions} options - the workflow options
242
- * @returns {Promise<string>} - the childJobId
243
- * @example
244
- * const childJobId = await Durable.workflow.startChild({ ...options });
245
- */
246
- static async startChild(options: WorkflowOptions): Promise<string> {
247
- return WorkflowService.execChild({ ...options, await: false });
248
- }
249
-
250
- /**
251
- * Wraps activities in a proxy that durably runs/re-runs them to completion.
252
- * TODO: verify that activities do not collide if named same on same server but bound to different workflows
253
- *
254
- * @param {ActivityConfig} options - the activity configuration
255
- * that will be used to wrap the activities.
256
- * @returns {ProxyType<ACT>} - a proxy object with the same keys as the
257
- * activities object, but with the values replaced by a wrapped function
258
- *
259
- * @example
260
- * // import the activities
261
- * import * as activities from './activities';
262
- * const proxy = WorkflowService.proxyActivities<typeof activities>({ activities });
263
- *
264
- * //or destructure the proxy object, as the function names are the keys
265
- * const { activity1, activity2 } = WorkflowService.proxyActivities<typeof activities>({ activities });
266
- */
267
- static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT> {
268
- if (options.activities) {
269
- WorkerService.registerActivities(options.activities);
270
- }
271
- const proxy: any = {};
272
- const keys = Object.keys(WorkerService.activityRegistry);
273
- if (keys.length) {
274
- keys.forEach((key: string) => {
275
- const activityFunction = WorkerService.activityRegistry[key];
276
- proxy[key] = WorkflowService.wrapActivity<typeof activityFunction>(key, options);
277
- });
278
- }
279
- return proxy;
280
- }
281
-
282
- static wrapActivity<T>(activityName: string, options?: ActivityConfig): T {
283
- return async function () {
284
- //SYNC
285
- //check if the activity already ran
286
- const [didRun, execIndex, result]: [boolean, number, ProxyResponseType<T>] = await WorkflowService.didRun('proxy');
287
- if (didRun) {
288
- if (result?.$error) {
289
- if (options?.retryPolicy?.throwOnError !== false) {
290
- //rethrow remote execution error (simulates throw)
291
- const code: StreamCode = result.$error.code;
292
- const message = result.$error.message;
293
- const stack = result.$error.stack;
294
- if (code === HMSH_CODE_DURABLE_FATAL) {
295
- throw new DurableFatalError(message, stack);
296
- } else if (code == HMSH_CODE_DURABLE_MAXED) {
297
- throw new DurableMaxedError(message, stack);
298
- } else if (code == HMSH_CODE_DURABLE_TIMEOUT) {
299
- throw new DurableTimeoutError(message, stack);
300
- }
301
- }
302
- return result.$error as T;
303
- }
304
- return result.data as T;
305
- }
306
- //package the interruption inputs
307
- const context = WorkflowService.getContext();
308
- const { interruptionRegistry } = context;
309
- const interruptionMessage = WorkflowService.getProxyInterruptPayload(
310
- context,
311
- activityName,
312
- execIndex,
313
- Array.from(arguments),
314
- options
315
- );
316
- //push the packaged inputs to the registry
317
- interruptionRegistry.push({
318
- code: HMSH_CODE_DURABLE_PROXY,
319
- ...interruptionMessage,
320
- });
321
- //ASYNC
322
- //sleep (allow others to be packaged / registered) and throw the error
323
- await sleepFor(0);
324
- throw new DurableProxyError(interruptionMessage);
325
- } as T;
326
- }
327
-
328
- /**
329
- * constructs the payload necessary to spawn a proxyActivity job
330
- * @private
331
- */
332
- static getProxyInterruptPayload(context: WorkflowContext, activityName: string, execIndex: number, args: any[], options?: ActivityConfig): DurableProxyErrorType {
333
- const { workflowDimension, workflowId, originJobId, workflowTopic } = context;
334
- const activityTopic = `${workflowTopic}-activity`;
335
- const activityJobId = `-${workflowId}-$${activityName}${workflowDimension}-${execIndex}`;
336
- let maximumInterval: number;
337
- if (options.retryPolicy?.maximumInterval) {
338
- maximumInterval = ms(options.retryPolicy.maximumInterval) / 1000;
339
- }
340
- return {
341
- arguments: args,
342
- workflowDimension: workflowDimension,
343
- index: execIndex,
344
- originJobId: originJobId || workflowId,
345
- parentWorkflowId: workflowId,
346
- workflowId: activityJobId,
347
- workflowTopic: activityTopic,
348
- activityName,
349
- backoffCoefficient: options?.retryPolicy?.backoffCoefficient ?? undefined,
350
- maximumAttempts: options?.retryPolicy?.maximumAttempts ?? undefined,
351
- maximumInterval: maximumInterval ?? undefined,
352
- };
353
- }
354
-
355
- /**
356
- * Returns a search session for use when reading/writing to the workflow HASH.
357
- * The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
358
- * @returns {Promise<Search>} - a search session
359
- */
360
- static async search(): Promise<Search> {
361
- const store = asyncLocalStorage.getStore();
362
- const workflowId = store.get('workflowId');
363
- const workflowDimension = store.get('workflowDimension') ?? '';
364
- const workflowTopic = store.get('workflowTopic');
365
- const namespace = store.get('namespace');
366
- const COUNTER = store.get('counter');
367
- const execIndex = COUNTER.counter = COUNTER.counter + 1;
368
- const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
369
- //this ID is used as a item key with a hash (dash prefix ensures no collision)
370
- const searchSessionId = `-search${workflowDimension}-${execIndex}`;
371
- return new Search(workflowId, hotMeshClient, searchSessionId);
372
- }
373
-
374
- /**
375
- * Returns a random number between 0 and 1. This number is deterministic
376
- * and will never vary for a given seed. This is useful for randomizing
377
- * pathways in a workflow that can be safely replayed.
378
- * @returns {number} - a random number between 0 and 1
379
- */
380
- static random(): number {
381
- const store = asyncLocalStorage.getStore();
382
- const COUNTER = store.get('counter');
383
- const seed = COUNTER.counter = COUNTER.counter + 1;
384
- return deterministicRandom(seed);
385
- }
386
-
387
- /**
388
- * Sends signal data into any other paused thread (which is currently
389
- * awaiting the signal)
390
- * @param {string} signalId - the signal id
391
- * @param {Record<any, any>} data - the signal data
392
- * @returns {Promise<string>} - the stream id
393
- */
394
- static async signal(signalId: string, data: Record<any, any>): Promise<string> {
395
- const store = asyncLocalStorage.getStore();
396
- const workflowTopic = store.get('workflowTopic');
397
- const namespace = store.get('namespace');
398
- const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
399
- if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'signal')) {
400
- return await hotMeshClient.hook(`${namespace}.wfs.signal`, { id: signalId, data });
401
- }
402
- }
403
-
404
- /**
405
- * Spawns a hook from either the main thread or a hook thread with
406
- * the provided options; worflowId/TaskQueue/Name are optional and will
407
- * default to the current workflowId/WorkflowTopic if not provided
408
- * @param {HookOptions} options - the hook options
409
- */
410
- static async hook(options: HookOptions): Promise<string> {
411
- const {
412
- workflowId,
413
- namespace,
414
- workflowTopic,
415
- } = WorkflowService.getContext();
416
- const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
417
- if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'hook')) {
418
- const targetWorkflowId = options.workflowId ?? workflowId;
419
- let targetTopic: string;
420
- if (options.entity || (options.taskQueue && options.workflowName)) {
421
- targetTopic = `${options.entity ?? options.taskQueue}-${options.entity ?? options.workflowName}`;
422
- } else {
423
- targetTopic = workflowTopic;
424
- }
425
- const payload = {
426
- arguments: [...options.args],
427
- id: targetWorkflowId,
428
- workflowTopic,
429
- backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
430
- }
431
- return await hotMeshClient.hook(`${namespace}.flow.signal`, payload, StreamStatus.PENDING, 202);
432
- }
433
- }
434
-
435
- /**
436
- * Executes a function once and caches the result. If the function is called
437
- * again, the cached result is returned. This is useful for wrapping
438
- * expensive activity calls that should only be run once, but which might
439
- * not require the cost and safety provided by proxyActivities.
440
- * @template T - the result type
441
- */
442
- static async once<T>(fn: (...args: any[]) => Promise<T>, ...args: any[]): Promise<T> {
443
- const {
444
- COUNTER,
445
- namespace,
446
- workflowId,
447
- workflowTopic,
448
- workflowDimension,
449
- replay,
450
- } = WorkflowService.getContext();
451
- const execIndex = COUNTER.counter = COUNTER.counter + 1;
452
- const sessionId = `-once${workflowDimension}-${execIndex}-`;
453
- if (sessionId in replay) {
454
- return SerializerService.fromString(replay[sessionId]).data as T;
455
- }
456
- const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
457
- const keyParams = {
458
- appId: hotMeshClient.appId,
459
- jobId: workflowId
460
- }
461
- const workflowGuid = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
462
- const t1 = new Date();
463
- const response = await fn(...args);
464
- const t2 = new Date();
465
- const payload = {
466
- data: response,
467
- ac: formatISODate(t1),
468
- au: formatISODate(t2),
469
- };
470
- await hotMeshClient.engine.store.exec('HSET', workflowGuid, sessionId, SerializerService.toString(payload));
471
- return response;
472
- }
473
-
474
- /**
475
- * Interrupts a running job
476
- */
477
- static async interrupt(jobId: string, options: JobInterruptOptions = {}): Promise<string | void> {
478
- const { workflowTopic, namespace } = WorkflowService.getContext();
479
- const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
480
- if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'interrupt')) {
481
- return await hotMeshClient.interrupt(`${hotMeshClient.appId}.execute`, jobId, options);
482
- }
483
- }
484
-
485
- /**
486
- * Sleeps the workflow for a duration. As the function is reentrant,
487
- * upon reentry, the function will traverse prior execution paths up
488
- * until the sleep command and then resume execution thereafter.
489
- * @param {string} duration - See the `ms` package for syntax examples: '1 minute', '2 hours', '3 days'
490
- * @returns {Promise<number>} - resolved duration in seconds
491
- */
492
- static async sleepFor(duration: string): Promise<number> {
493
- //SYNC
494
- //return early if this sleep command has already run
495
- const [didRun, execIndex, result] = await WorkflowService.didRun('sleep');
496
- if (didRun) {
497
- return (result as { completion: string, duration: number }).duration; //in seconds
498
- }
499
- //package the interruption inputs
500
- const store = asyncLocalStorage.getStore();
501
- const interruptionRegistry = store.get('interruptionRegistry');
502
- const workflowId = store.get('workflowId');
503
- const workflowDimension = store.get('workflowDimension') ?? '';
504
- const interruptionMessage = {
505
- workflowId,
506
- duration: ms(duration) / 1000,
507
- index: execIndex,
508
- workflowDimension,
509
- }
510
- interruptionRegistry.push({
511
- code: HMSH_CODE_DURABLE_SLEEP,
512
- ...interruptionMessage,
513
- });
514
- //ASYNC
515
- //sleep to allow other interruptions to be packaged and registered
516
- await sleepFor(0);
517
- // NOTE: If you are reading this in the stack trace, await `sleepFor`
518
- throw new DurableSleepError(interruptionMessage);
519
- }
520
-
521
- /**
522
- * Pauses the workflow until `signalId` is received.
523
- * @template T - the result type
524
- * @param {string} signalId - a unique, shareable guid (e.g, 'abc123')
525
- * @returns {Promise<T>}
526
- * @example
527
- * const result = await Durable.workflow.waitFor<typeof resultType>('abc123');
528
- */
529
- static async waitFor<T>(signalId: string): Promise<T> {
530
- //SYNC
531
- //return early if this waitFor command has already run
532
- const [didRun, execIndex, result] = await WorkflowService.didRun('wait');
533
- if (didRun) {
534
- return (result as { id: string, data: { data: T }}).data.data as T;
535
- }
536
- //package the interruption inputs
537
- const store = asyncLocalStorage.getStore();
538
- const interruptionRegistry = store.get('interruptionRegistry');
539
- const workflowId = store.get('workflowId');
540
- const workflowDimension = store.get('workflowDimension') ?? '';
541
- const interruptionMessage = {
542
- workflowId,
543
- signalId,
544
- index: execIndex,
545
- workflowDimension,
546
- }
547
- interruptionRegistry.push({
548
- code: HMSH_CODE_DURABLE_WAIT,
549
- ...interruptionMessage,
550
- });
551
- //ASYNC
552
- //sleep to allow other interruptions to be packaged and registered
553
- await sleepFor(0);
554
- // NOTE: If you are reading this in the stack trace, await `waitFor`
555
- throw new DurableWaitForError(interruptionMessage);
556
- }
557
- }