@hotmeshio/hotmesh 0.0.57 → 0.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +10 -10
  2. package/build/modules/enums.js +10 -1
  3. package/build/modules/key.d.ts +38 -0
  4. package/build/modules/key.js +46 -4
  5. package/build/modules/utils.d.ts +9 -0
  6. package/build/modules/utils.js +19 -1
  7. package/build/package.json +1 -1
  8. package/build/services/activities/activity.d.ts +28 -0
  9. package/build/services/activities/activity.js +46 -1
  10. package/build/services/activities/await.js +4 -0
  11. package/build/services/activities/cycle.d.ts +7 -0
  12. package/build/services/activities/cycle.js +16 -1
  13. package/build/services/activities/hook.d.ts +6 -0
  14. package/build/services/activities/hook.js +12 -2
  15. package/build/services/activities/interrupt.js +8 -0
  16. package/build/services/activities/signal.d.ts +6 -0
  17. package/build/services/activities/signal.js +15 -0
  18. package/build/services/activities/trigger.d.ts +4 -0
  19. package/build/services/activities/trigger.js +7 -1
  20. package/build/services/activities/worker.js +4 -0
  21. package/build/services/collator/index.d.ts +70 -0
  22. package/build/services/collator/index.js +91 -1
  23. package/build/services/compiler/deployer.js +38 -6
  24. package/build/services/compiler/index.d.ts +15 -0
  25. package/build/services/compiler/index.js +20 -0
  26. package/build/services/compiler/validator.d.ts +3 -0
  27. package/build/services/compiler/validator.js +25 -0
  28. package/build/services/connector/clients/ioredis.js +2 -0
  29. package/build/services/connector/clients/redis.js +2 -0
  30. package/build/services/connector/index.js +2 -0
  31. package/build/services/durable/client.d.ts +20 -0
  32. package/build/services/durable/client.js +25 -0
  33. package/build/services/durable/exporter.d.ts +22 -0
  34. package/build/services/durable/exporter.js +30 -1
  35. package/build/services/durable/handle.d.ts +36 -0
  36. package/build/services/durable/handle.js +46 -0
  37. package/build/services/durable/index.d.ts +4 -0
  38. package/build/services/durable/index.js +4 -0
  39. package/build/services/durable/schemas/factory.d.ts +29 -0
  40. package/build/services/durable/schemas/factory.js +29 -0
  41. package/build/services/durable/search.d.ts +97 -0
  42. package/build/services/durable/search.js +99 -0
  43. package/build/services/durable/worker.js +35 -6
  44. package/build/services/durable/workflow.d.ts +118 -0
  45. package/build/services/durable/workflow.js +153 -6
  46. package/build/services/engine/index.d.ts +5 -0
  47. package/build/services/engine/index.js +43 -1
  48. package/build/services/exporter/index.d.ts +27 -0
  49. package/build/services/exporter/index.js +33 -0
  50. package/build/services/hotmesh/index.js +8 -0
  51. package/build/services/logger/index.js +2 -0
  52. package/build/services/mapper/index.d.ts +14 -0
  53. package/build/services/mapper/index.js +14 -0
  54. package/build/services/pipe/functions/date.d.ts +7 -0
  55. package/build/services/pipe/functions/date.js +7 -0
  56. package/build/services/pipe/functions/math.js +2 -0
  57. package/build/services/pipe/index.d.ts +15 -0
  58. package/build/services/pipe/index.js +23 -2
  59. package/build/services/quorum/index.d.ts +7 -0
  60. package/build/services/quorum/index.js +21 -0
  61. package/build/services/reporter/index.d.ts +5 -0
  62. package/build/services/reporter/index.js +9 -0
  63. package/build/services/router/index.d.ts +9 -0
  64. package/build/services/router/index.js +30 -2
  65. package/build/services/serializer/index.js +23 -6
  66. package/build/services/store/cache.d.ts +19 -0
  67. package/build/services/store/cache.js +19 -0
  68. package/build/services/store/clients/ioredis.js +1 -0
  69. package/build/services/store/index.d.ts +55 -0
  70. package/build/services/store/index.js +81 -5
  71. package/build/services/stream/clients/ioredis.js +4 -1
  72. package/build/services/task/index.d.ts +9 -0
  73. package/build/services/task/index.js +31 -0
  74. package/build/services/telemetry/index.d.ts +7 -0
  75. package/build/services/telemetry/index.js +13 -1
  76. package/build/services/worker/index.d.ts +4 -0
  77. package/build/services/worker/index.js +6 -2
  78. package/build/types/activity.d.ts +81 -0
  79. package/build/types/durable.d.ts +255 -0
  80. package/build/types/exporter.d.ts +13 -0
  81. package/build/types/hotmesh.d.ts +10 -1
  82. package/build/types/hotmesh.js +3 -0
  83. package/build/types/index.js +1 -1
  84. package/build/types/job.d.ts +85 -0
  85. package/build/types/pipe.d.ts +65 -0
  86. package/build/types/quorum.d.ts +14 -0
  87. package/build/types/redis.d.ts +6 -0
  88. package/build/types/stream.d.ts +58 -0
  89. package/build/types/stream.js +4 -0
  90. package/package.json +1 -1
@@ -4,22 +4,140 @@ import { ActivityConfig, HookOptions, ProxyType, WorkflowContext, WorkflowOption
4
4
  import { JobInterruptOptions } from '../../types/job';
5
5
  import { DurableChildErrorType, DurableProxyErrorType } from '../../types/error';
6
6
  export declare class WorkflowService {
7
+ /**
8
+ * Returns the synchronous output from the activity (replay)
9
+ * if available locally, revealing whether or not the activity already
10
+ * ran during a prior execution cycle
11
+ * @param {string} prefix - one of: proxy, child, start, wait etc
12
+ * @returns
13
+ */
7
14
  static didRun(prefix: string): Promise<[boolean, number, any]>;
15
+ /**
16
+ * Those methods that may only be called once must be protected by flagging
17
+ * their execution with a unique key (the key is stored in the HASH alongside
18
+ * process state and job state)
19
+ * @private
20
+ */
8
21
  static isSideEffectAllowed(hotMeshClient: HotMesh, prefix: string): Promise<boolean>;
22
+ /**
23
+ * Returns the current workflow context restored
24
+ * from Redis
25
+ */
9
26
  static getContext(): WorkflowContext;
27
+ /**
28
+ * Return a handle to the hotmesh client hosting the workflow execution
29
+ * @returns {Promise<HotMesh>} - a hotmesh client
30
+ */
10
31
  static getHotMesh(): Promise<HotMesh>;
32
+ /**
33
+ * Spawns a child workflow and awaits the return.
34
+ * @template T - the result type
35
+ * @param {WorkflowOptions} options - the workflow options
36
+ * @returns {Promise<T>} - the result of the child workflow
37
+ * @example
38
+ * const result = await Durable.workflow.execChild<typeof resultType>({ ...options });
39
+ */
11
40
  static execChild<T>(options: WorkflowOptions): Promise<T>;
41
+ /**
42
+ * constructs the payload necessary to spawn a child job
43
+ * @private
44
+ */
12
45
  static getChildInterruptPayload(context: WorkflowContext, options: WorkflowOptions, execIndex: number): DurableChildErrorType;
46
+ /**
47
+ * Spawns a child workflow and returns the child Job ID.
48
+ * This method guarantees the spawned child has reserved the Job ID,
49
+ * returning a 'DuplicateJobError' error if not. Otherwise,
50
+ * this is a fire-and-forget method.
51
+ *
52
+ * @param {WorkflowOptions} options - the workflow options
53
+ * @returns {Promise<string>} - the childJobId
54
+ * @example
55
+ * const childJobId = await Durable.workflow.startChild({ ...options });
56
+ */
13
57
  static startChild(options: WorkflowOptions): Promise<string>;
58
+ /**
59
+ * Wraps activities in a proxy that durably runs/re-runs them to completion.
60
+ * TODO: verify that activities do not collide if named same on same server but bound to different workflows
61
+ *
62
+ * @param {ActivityConfig} options - the activity configuration
63
+ * that will be used to wrap the activities.
64
+ * @returns {ProxyType<ACT>} - a proxy object with the same keys as the
65
+ * activities object, but with the values replaced by a wrapped function
66
+ *
67
+ * @example
68
+ * // import the activities
69
+ * import * as activities from './activities';
70
+ * const proxy = WorkflowService.proxyActivities<typeof activities>({ activities });
71
+ *
72
+ * //or destructure the proxy object, as the function names are the keys
73
+ * const { activity1, activity2 } = WorkflowService.proxyActivities<typeof activities>({ activities });
74
+ */
14
75
  static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
15
76
  static wrapActivity<T>(activityName: string, options?: ActivityConfig): T;
77
+ /**
78
+ * constructs the payload necessary to spawn a proxyActivity job
79
+ * @private
80
+ */
16
81
  static getProxyInterruptPayload(context: WorkflowContext, activityName: string, execIndex: number, args: any[], options?: ActivityConfig): DurableProxyErrorType;
82
+ /**
83
+ * Returns a search session for use when reading/writing to the workflow HASH.
84
+ * The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
85
+ * @returns {Promise<Search>} - a search session
86
+ */
17
87
  static search(): Promise<Search>;
88
+ /**
89
+ * Returns a random number between 0 and 1. This number is deterministic
90
+ * and will never vary for a given seed. This is useful for randomizing
91
+ * pathways in a workflow that can be safely replayed.
92
+ * @returns {number} - a random number between 0 and 1
93
+ */
18
94
  static random(): number;
95
+ /**
96
+ * Sends signal data into any other paused thread (which is currently
97
+ * awaiting the signal)
98
+ * @param {string} signalId - the signal id
99
+ * @param {Record<any, any>} data - the signal data
100
+ * @returns {Promise<string>} - the stream id
101
+ */
19
102
  static signal(signalId: string, data: Record<any, any>): Promise<string>;
103
+ /**
104
+ * Spawns a hook from either the main thread or a hook thread with
105
+ * the provided options; worflowId/TaskQueue/Name are optional and will
106
+ * default to the current workflowId/WorkflowTopic if not provided
107
+ * @param {HookOptions} options - the hook options
108
+ */
20
109
  static hook(options: HookOptions): Promise<string>;
110
+ /**
111
+ * Executes a function once and caches the result. If the function is called
112
+ * again, the cached result is returned. This is useful for wrapping
113
+ * expensive activity calls that should only be run once, but which might
114
+ * not require the cost and safety provided by proxyActivities.
115
+ * @template T - the result type
116
+ */
21
117
  static once<T>(fn: (...args: any[]) => Promise<T>, ...args: any[]): Promise<T>;
118
+ /**
119
+ * Interrupts a running job
120
+ */
22
121
  static interrupt(jobId: string, options?: JobInterruptOptions): Promise<string | void>;
122
+ /**
123
+ * Promise.all (limited to 25 total concurrent workflow)
124
+ */
125
+ static all<T>(...promises: Promise<T>[]): Promise<T[]>;
126
+ /**
127
+ * Sleeps the workflow for a duration. As the function is reentrant,
128
+ * upon reentry, the function will traverse prior execution paths up
129
+ * until the sleep command and then resume execution thereafter.
130
+ * @param {string} duration - See the `ms` package for syntax examples: '1 minute', '2 hours', '3 days'
131
+ * @returns {Promise<number>} - resolved duration in seconds
132
+ */
23
133
  static sleepFor(duration: string): Promise<number>;
134
+ /**
135
+ * Pauses the workflow until `signalId` is received.
136
+ * @template T - the result type
137
+ * @param {string} signalId - a unique, shareable guid (e.g, 'abc123')
138
+ * @returns {Promise<T>}
139
+ * @example
140
+ * const result = await Durable.workflow.waitFor<typeof resultType>('abc123');
141
+ */
24
142
  static waitFor<T>(signalId: string): Promise<T>;
25
143
  }
@@ -15,6 +15,13 @@ const serializer_1 = require("../serializer");
15
15
  const stream_1 = require("../../types/stream");
16
16
  const enums_1 = require("../../modules/enums");
17
17
  class WorkflowService {
18
+ /**
19
+ * Returns the synchronous output from the activity (replay)
20
+ * if available locally, revealing whether or not the activity already
21
+ * ran during a prior execution cycle
22
+ * @param {string} prefix - one of: proxy, child, start, wait etc
23
+ * @returns
24
+ */
18
25
  static async didRun(prefix) {
19
26
  const { COUNTER, replay, workflowDimension, } = WorkflowService.getContext();
20
27
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
@@ -25,6 +32,12 @@ class WorkflowService {
25
32
  }
26
33
  return [false, execIndex, null];
27
34
  }
35
+ /**
36
+ * Those methods that may only be called once must be protected by flagging
37
+ * their execution with a unique key (the key is stored in the HASH alongside
38
+ * process state and job state)
39
+ * @private
40
+ */
28
41
  static async isSideEffectAllowed(hotMeshClient, prefix) {
29
42
  const store = storage_1.asyncLocalStorage.getStore();
30
43
  const workflowId = store.get('workflowId');
@@ -44,6 +57,10 @@ class WorkflowService {
44
57
  const guidValue = Number(await hotMeshClient.engine.store.exec('HINCRBYFLOAT', workflowGuid, sessionId, '1'));
45
58
  return guidValue === 1;
46
59
  }
60
+ /**
61
+ * Returns the current workflow context restored
62
+ * from Redis
63
+ */
47
64
  static getContext() {
48
65
  const store = storage_1.asyncLocalStorage.getStore();
49
66
  const workflowId = store.get('workflowId');
@@ -76,13 +93,27 @@ class WorkflowService {
76
93
  workflowSpan,
77
94
  };
78
95
  }
96
+ /**
97
+ * Return a handle to the hotmesh client hosting the workflow execution
98
+ * @returns {Promise<HotMesh>} - a hotmesh client
99
+ */
79
100
  static async getHotMesh() {
80
101
  const store = storage_1.asyncLocalStorage.getStore();
81
102
  const workflowTopic = store.get('workflowTopic');
82
103
  const namespace = store.get('namespace');
83
104
  return await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
84
105
  }
106
+ /**
107
+ * Spawns a child workflow and awaits the return.
108
+ * @template T - the result type
109
+ * @param {WorkflowOptions} options - the workflow options
110
+ * @returns {Promise<T>} - the result of the child workflow
111
+ * @example
112
+ * const result = await Durable.workflow.execChild<typeof resultType>({ ...options });
113
+ */
85
114
  static async execChild(options) {
115
+ //SYNC
116
+ //check if the activity already ran (check $error/done)
86
117
  const isStartChild = options.await === false;
87
118
  const prefix = isStartChild ? 'start' : 'child';
88
119
  const [didRun, execIndex, result] = await WorkflowService.didRun(prefix);
@@ -91,6 +122,7 @@ class WorkflowService {
91
122
  if (didRun) {
92
123
  if (result?.$error && (!result.$error.is_stream_error || (result.$error.is_stream_error && !canRetry))) {
93
124
  if (options?.config?.throwOnError !== false) {
125
+ //rethrow remote execution error (simulates local failure)
94
126
  const code = result.$error.code;
95
127
  const message = result.$error.message;
96
128
  const stack = result.$error.stack;
@@ -114,13 +146,20 @@ class WorkflowService {
114
146
  }
115
147
  }
116
148
  const interruptionMessage = WorkflowService.getChildInterruptPayload(context, options, execIndex);
149
+ //push the packaged inputs to the registry
117
150
  interruptionRegistry.push({
118
151
  code: enums_1.HMSH_CODE_DURABLE_CHILD,
119
152
  ...interruptionMessage,
120
153
  });
121
- await (0, utils_1.sleepFor)(0);
154
+ //ASYNC
155
+ //sleep (allow others to be packaged / registered) and throw the error
156
+ await (0, utils_1.sleepImmediate)();
122
157
  throw new errors_1.DurableChildError(interruptionMessage);
123
158
  }
159
+ /**
160
+ * constructs the payload necessary to spawn a child job
161
+ * @private
162
+ */
124
163
  static getChildInterruptPayload(context, options, execIndex) {
125
164
  const { workflowId, originJobId, workflowDimension } = context;
126
165
  let childJobId;
@@ -134,7 +173,7 @@ class WorkflowService {
134
173
  childJobId = `-${options.workflowName}-${(0, utils_1.guid)()}-${workflowDimension}-${execIndex}`;
135
174
  }
136
175
  const parentWorkflowId = workflowId;
137
- const taskQueueName = options.entity ?? options.taskQueue;
176
+ const taskQueueName = options.taskQueue ?? options.entity;
138
177
  const workflowName = options.entity ?? options.workflowName;
139
178
  const workflowTopic = `${taskQueueName}-${workflowName}`;
140
179
  return {
@@ -151,9 +190,37 @@ class WorkflowService {
151
190
  workflowTopic,
152
191
  };
153
192
  }
193
+ /**
194
+ * Spawns a child workflow and returns the child Job ID.
195
+ * This method guarantees the spawned child has reserved the Job ID,
196
+ * returning a 'DuplicateJobError' error if not. Otherwise,
197
+ * this is a fire-and-forget method.
198
+ *
199
+ * @param {WorkflowOptions} options - the workflow options
200
+ * @returns {Promise<string>} - the childJobId
201
+ * @example
202
+ * const childJobId = await Durable.workflow.startChild({ ...options });
203
+ */
154
204
  static async startChild(options) {
155
205
  return WorkflowService.execChild({ ...options, await: false });
156
206
  }
207
+ /**
208
+ * Wraps activities in a proxy that durably runs/re-runs them to completion.
209
+ * TODO: verify that activities do not collide if named same on same server but bound to different workflows
210
+ *
211
+ * @param {ActivityConfig} options - the activity configuration
212
+ * that will be used to wrap the activities.
213
+ * @returns {ProxyType<ACT>} - a proxy object with the same keys as the
214
+ * activities object, but with the values replaced by a wrapped function
215
+ *
216
+ * @example
217
+ * // import the activities
218
+ * import * as activities from './activities';
219
+ * const proxy = WorkflowService.proxyActivities<typeof activities>({ activities });
220
+ *
221
+ * //or destructure the proxy object, as the function names are the keys
222
+ * const { activity1, activity2 } = WorkflowService.proxyActivities<typeof activities>({ activities });
223
+ */
157
224
  static proxyActivities(options) {
158
225
  if (options.activities) {
159
226
  worker_1.WorkerService.registerActivities(options.activities);
@@ -170,10 +237,13 @@ class WorkflowService {
170
237
  }
171
238
  static wrapActivity(activityName, options) {
172
239
  return async function () {
240
+ //SYNC
241
+ //check if the activity already ran
173
242
  const [didRun, execIndex, result] = await WorkflowService.didRun('proxy');
174
243
  if (didRun) {
175
244
  if (result?.$error) {
176
245
  if (options?.retryPolicy?.throwOnError !== false) {
246
+ //rethrow remote execution error (simulates throw)
177
247
  const code = result.$error.code;
178
248
  const message = result.$error.message;
179
249
  const stack = result.$error.stack;
@@ -191,17 +261,25 @@ class WorkflowService {
191
261
  }
192
262
  return result.data;
193
263
  }
264
+ //package the interruption inputs
194
265
  const context = WorkflowService.getContext();
195
266
  const { interruptionRegistry } = context;
196
267
  const interruptionMessage = WorkflowService.getProxyInterruptPayload(context, activityName, execIndex, Array.from(arguments), options);
268
+ //push the packaged inputs to the registry
197
269
  interruptionRegistry.push({
198
270
  code: enums_1.HMSH_CODE_DURABLE_PROXY,
199
271
  ...interruptionMessage,
200
272
  });
201
- await (0, utils_1.sleepFor)(0);
273
+ //ASYNC
274
+ //sleep (allow others to be packaged / registered) and throw the error
275
+ await (0, utils_1.sleepImmediate)();
202
276
  throw new errors_1.DurableProxyError(interruptionMessage);
203
277
  };
204
278
  }
279
+ /**
280
+ * constructs the payload necessary to spawn a proxyActivity job
281
+ * @private
282
+ */
205
283
  static getProxyInterruptPayload(context, activityName, execIndex, args, options) {
206
284
  const { workflowDimension, workflowId, originJobId, workflowTopic } = context;
207
285
  const activityTopic = `${workflowTopic}-activity`;
@@ -224,6 +302,11 @@ class WorkflowService {
224
302
  maximumInterval: maximumInterval ?? undefined,
225
303
  };
226
304
  }
305
+ /**
306
+ * Returns a search session for use when reading/writing to the workflow HASH.
307
+ * The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
308
+ * @returns {Promise<Search>} - a search session
309
+ */
227
310
  static async search() {
228
311
  const store = storage_1.asyncLocalStorage.getStore();
229
312
  const workflowId = store.get('workflowId');
@@ -233,15 +316,29 @@ class WorkflowService {
233
316
  const COUNTER = store.get('counter');
234
317
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
235
318
  const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
319
+ //this ID is used as a item key with a hash (dash prefix ensures no collision)
236
320
  const searchSessionId = `-search${workflowDimension}-${execIndex}`;
237
321
  return new search_1.Search(workflowId, hotMeshClient, searchSessionId);
238
322
  }
323
+ /**
324
+ * Returns a random number between 0 and 1. This number is deterministic
325
+ * and will never vary for a given seed. This is useful for randomizing
326
+ * pathways in a workflow that can be safely replayed.
327
+ * @returns {number} - a random number between 0 and 1
328
+ */
239
329
  static random() {
240
330
  const store = storage_1.asyncLocalStorage.getStore();
241
331
  const COUNTER = store.get('counter');
242
332
  const seed = COUNTER.counter = COUNTER.counter + 1;
243
333
  return (0, utils_1.deterministicRandom)(seed);
244
334
  }
335
+ /**
336
+ * Sends signal data into any other paused thread (which is currently
337
+ * awaiting the signal)
338
+ * @param {string} signalId - the signal id
339
+ * @param {Record<any, any>} data - the signal data
340
+ * @returns {Promise<string>} - the stream id
341
+ */
245
342
  static async signal(signalId, data) {
246
343
  const store = storage_1.asyncLocalStorage.getStore();
247
344
  const workflowTopic = store.get('workflowTopic');
@@ -251,6 +348,12 @@ class WorkflowService {
251
348
  return await hotMeshClient.hook(`${namespace}.wfs.signal`, { id: signalId, data });
252
349
  }
253
350
  }
351
+ /**
352
+ * Spawns a hook from either the main thread or a hook thread with
353
+ * the provided options; worflowId/TaskQueue/Name are optional and will
354
+ * default to the current workflowId/WorkflowTopic if not provided
355
+ * @param {HookOptions} options - the hook options
356
+ */
254
357
  static async hook(options) {
255
358
  const { workflowId, namespace, workflowTopic, } = WorkflowService.getContext();
256
359
  const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
@@ -272,6 +375,13 @@ class WorkflowService {
272
375
  return await hotMeshClient.hook(`${namespace}.flow.signal`, payload, stream_1.StreamStatus.PENDING, 202);
273
376
  }
274
377
  }
378
+ /**
379
+ * Executes a function once and caches the result. If the function is called
380
+ * again, the cached result is returned. This is useful for wrapping
381
+ * expensive activity calls that should only be run once, but which might
382
+ * not require the cost and safety provided by proxyActivities.
383
+ * @template T - the result type
384
+ */
275
385
  static async once(fn, ...args) {
276
386
  const { COUNTER, namespace, workflowId, workflowTopic, workflowDimension, replay, } = WorkflowService.getContext();
277
387
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
@@ -296,6 +406,9 @@ class WorkflowService {
296
406
  await hotMeshClient.engine.store.exec('HSET', workflowGuid, sessionId, serializer_1.SerializerService.toString(payload));
297
407
  return response;
298
408
  }
409
+ /**
410
+ * Interrupts a running job
411
+ */
299
412
  static async interrupt(jobId, options = {}) {
300
413
  const { workflowTopic, namespace } = WorkflowService.getContext();
301
414
  const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
@@ -303,11 +416,28 @@ class WorkflowService {
303
416
  return await hotMeshClient.interrupt(`${hotMeshClient.appId}.execute`, jobId, options);
304
417
  }
305
418
  }
419
+ /**
420
+ * Promise.all (limited to 25 total concurrent workflow)
421
+ */
422
+ static async all(...promises) {
423
+ await new Promise((resolve) => setTimeout(resolve, 1));
424
+ return await Promise.all(promises);
425
+ }
426
+ /**
427
+ * Sleeps the workflow for a duration. As the function is reentrant,
428
+ * upon reentry, the function will traverse prior execution paths up
429
+ * until the sleep command and then resume execution thereafter.
430
+ * @param {string} duration - See the `ms` package for syntax examples: '1 minute', '2 hours', '3 days'
431
+ * @returns {Promise<number>} - resolved duration in seconds
432
+ */
306
433
  static async sleepFor(duration) {
434
+ //SYNC
435
+ //return early if this sleep command has already run
307
436
  const [didRun, execIndex, result] = await WorkflowService.didRun('sleep');
308
437
  if (didRun) {
309
- return result.duration;
438
+ return result.duration; //in seconds
310
439
  }
440
+ //package the interruption inputs
311
441
  const store = storage_1.asyncLocalStorage.getStore();
312
442
  const interruptionRegistry = store.get('interruptionRegistry');
313
443
  const workflowId = store.get('workflowId');
@@ -322,14 +452,28 @@ class WorkflowService {
322
452
  code: enums_1.HMSH_CODE_DURABLE_SLEEP,
323
453
  ...interruptionMessage,
324
454
  });
325
- await (0, utils_1.sleepFor)(0);
455
+ //ASYNC
456
+ //sleep to allow other interruptions to be packaged and registered
457
+ await (0, utils_1.sleepImmediate)();
458
+ // NOTE: If you are reading this in the stack trace, await `sleepFor`
326
459
  throw new errors_1.DurableSleepError(interruptionMessage);
327
460
  }
461
+ /**
462
+ * Pauses the workflow until `signalId` is received.
463
+ * @template T - the result type
464
+ * @param {string} signalId - a unique, shareable guid (e.g, 'abc123')
465
+ * @returns {Promise<T>}
466
+ * @example
467
+ * const result = await Durable.workflow.waitFor<typeof resultType>('abc123');
468
+ */
328
469
  static async waitFor(signalId) {
470
+ //SYNC
471
+ //return early if this waitFor command has already run
329
472
  const [didRun, execIndex, result] = await WorkflowService.didRun('wait');
330
473
  if (didRun) {
331
474
  return result.data.data;
332
475
  }
476
+ //package the interruption inputs
333
477
  const store = storage_1.asyncLocalStorage.getStore();
334
478
  const interruptionRegistry = store.get('interruptionRegistry');
335
479
  const workflowId = store.get('workflowId');
@@ -344,7 +488,10 @@ class WorkflowService {
344
488
  code: enums_1.HMSH_CODE_DURABLE_WAIT,
345
489
  ...interruptionMessage,
346
490
  });
347
- await (0, utils_1.sleepFor)(0);
491
+ //ASYNC
492
+ //sleep to allow other interruptions to be packaged and registered
493
+ await (0, utils_1.sleepImmediate)();
494
+ // NOTE: If you are reading this in the stack trace, await `waitFor`
348
495
  throw new errors_1.DurableWaitForError(interruptionMessage);
349
496
  }
350
497
  }
@@ -86,6 +86,11 @@ declare class EngineService {
86
86
  delistJobCallback(jobId: string): void;
87
87
  hasOneTimeSubscription(context: JobState): boolean;
88
88
  runJobCompletionTasks(context: JobState, options?: JobCompletionOptions): Promise<string | void>;
89
+ /**
90
+ * Job hash expiration is typically reliant on the metadata field
91
+ * if the activity concludes normally. However, if the job is `interrupted`,
92
+ * it will be expired immediately.
93
+ */
89
94
  resolveExpires(context: JobState, options: JobCompletionOptions): number;
90
95
  export(jobId: string): Promise<JobExport>;
91
96
  getRaw(jobId: string): Promise<StringStringType>;