@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,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
- }