@hotmeshio/hotmesh 0.0.32 → 0.0.33
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.
- package/README.md +70 -22
- package/build/modules/errors.d.ts +8 -1
- package/build/modules/errors.js +12 -1
- package/build/package.json +1 -1
- package/build/services/durable/client.js +3 -4
- package/build/services/durable/factory.d.ts +2 -1
- package/build/services/durable/factory.js +66 -2
- package/build/services/durable/worker.js +16 -1
- package/build/services/durable/workflow.d.ts +12 -1
- package/build/services/durable/workflow.js +28 -2
- package/modules/errors.ts +15 -0
- package/package.json +1 -1
- package/services/durable/client.ts +3 -4
- package/services/durable/factory.ts +66 -2
- package/services/durable/worker.ts +17 -1
- package/services/durable/workflow.ts +30 -2
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ npm install @hotmeshio/hotmesh
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
|
-
3.
|
|
47
|
+
3. Instance a HotMesh **client** to invoke the workflow.
|
|
48
48
|
```javascript
|
|
49
49
|
//client.ts
|
|
50
50
|
import { Durable } from '@hotmeshio/hotmesh';
|
|
@@ -91,29 +91,77 @@ npm install @hotmeshio/hotmesh
|
|
|
91
91
|
}
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
### Workflow Extensions
|
|
94
95
|
Redis governance delivers more than just reliability. Externalizing state fundamentally changes the execution profile for your functions, allowing you to design long-running, durable workflows. The `Durable.workflow` base class (shown in the examples above) provides additional methods for solving the most common state management challenges.
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- `
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- `
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
- `
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- `
|
|
97
|
+
- `waitForSignal` Pause your function and wait for external event(s) before continuing. The *waitForSignal* method will collate and cache the signals and only awaken your function once all signals have arrived.
|
|
98
|
+
```javascript
|
|
99
|
+
const signals = [a, b] = await Durable.workflow.waitForSignal('sig1', 'sig2')`
|
|
100
|
+
```
|
|
101
|
+
- `signal` Send a signal (and optional payload) to a paused function awaiting the signal.
|
|
102
|
+
```javascript
|
|
103
|
+
await Durable.workflow.signal('sig1', {payload: 'hi!'});
|
|
104
|
+
```
|
|
105
|
+
- `hook` Redis governance converts your functions into 're-entrant processes'. Optionally use the *hook* method to spawn parallel execution threads to augment a running workflow.
|
|
106
|
+
```javascript
|
|
107
|
+
await Durable.workflow.hook({
|
|
108
|
+
workflowName: 'newsletter',
|
|
109
|
+
taskQueue: 'default',
|
|
110
|
+
args: []
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
- `sleepFor` Pause function execution for a ridiculous amount of time (months, years, etc). There's no risk of information loss, as Redis governs function state. When your function awakens, function state is efficiently (and automatically) restored and your function will resume right where it left off.
|
|
114
|
+
```javascript
|
|
115
|
+
await Durable.workflow.sleepFor('1 month');
|
|
116
|
+
```
|
|
117
|
+
- `random` Generate a deterministic random number that can be used in a reentrant process workflow (replaces `Math.random()`).
|
|
118
|
+
```javascript
|
|
119
|
+
const random = await Durable.workflow.random();
|
|
120
|
+
```
|
|
121
|
+
- `executeChild` Call another durable function and await the response. *Design sophisticated, multi-process solutions by leveraging this command.*
|
|
122
|
+
```javascript
|
|
123
|
+
const jobResponse = await Durable.workflow.executeChild({
|
|
124
|
+
workflowName: 'newsletter',
|
|
125
|
+
taskQueue: 'default',
|
|
126
|
+
args: [{ id, user_id, etc }],
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
- `startChild` Call another durable function, but do not await the response.
|
|
130
|
+
```javascript
|
|
131
|
+
const jobId = await Durable.workflow.startChild({
|
|
132
|
+
workflowName: 'newsletter',
|
|
133
|
+
taskQueue: 'default',
|
|
134
|
+
args: [{ id, user_id, etc }],
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
- `search` Instance a search session
|
|
138
|
+
```javascript
|
|
139
|
+
const search = await Durable.workflow.search();
|
|
140
|
+
```
|
|
141
|
+
- `set` Set one or more name/value pairs
|
|
142
|
+
```javascript
|
|
143
|
+
await search.set('name1', 'value1', 'name2', 'value2');
|
|
144
|
+
```
|
|
145
|
+
- `get` Get a single value by name
|
|
146
|
+
```javascript
|
|
147
|
+
const value = await search.get('name');
|
|
148
|
+
```
|
|
149
|
+
- `mget` Get multiple values by name
|
|
150
|
+
```javascript
|
|
151
|
+
const [val1, val2] = await search.mget('name1', 'name2');
|
|
152
|
+
```
|
|
153
|
+
- `del` Delete one or more entries by name and return the number deleted
|
|
154
|
+
```javascript
|
|
155
|
+
const count = await search.del('name1', 'name2');
|
|
156
|
+
```
|
|
157
|
+
- `incr` Increment (or decrement) a number
|
|
158
|
+
```javascript
|
|
159
|
+
const value = await search.incr('name', 12);
|
|
160
|
+
```
|
|
161
|
+
- `mult` Multiply a number
|
|
162
|
+
```javascript
|
|
163
|
+
const value = await search.mult('name', 12);
|
|
164
|
+
```
|
|
117
165
|
|
|
118
166
|
Refer to the [hotmeshio/samples-typescript](https://github.com/hotmeshio/samples-typescript) repo for usage examples.
|
|
119
167
|
|
|
@@ -28,6 +28,13 @@ declare class DurableSleepError extends Error {
|
|
|
28
28
|
dimension: string;
|
|
29
29
|
constructor(message: string, duration: number, index: number, dimension: string);
|
|
30
30
|
}
|
|
31
|
+
declare class DurableSleepForError extends Error {
|
|
32
|
+
code: number;
|
|
33
|
+
duration: number;
|
|
34
|
+
index: number;
|
|
35
|
+
dimension: string;
|
|
36
|
+
constructor(message: string, duration: number, index: number, dimension: string);
|
|
37
|
+
}
|
|
31
38
|
declare class DurableTimeoutError extends Error {
|
|
32
39
|
code: number;
|
|
33
40
|
constructor(message: string);
|
|
@@ -63,4 +70,4 @@ declare class CollationError extends Error {
|
|
|
63
70
|
fault: CollationFaultType;
|
|
64
71
|
constructor(status: number, leg: ActivityDuplex, stage: CollationStage, fault?: CollationFaultType);
|
|
65
72
|
}
|
|
66
|
-
export { CollationError, DurableTimeoutError, DurableMaxedError, DurableFatalError, DurableRetryError, DurableWaitForSignalError, DurableIncompleteSignalError, DurableSleepError, DuplicateJobError, GetStateError, SetStateError, MapDataError, RegisterTimeoutError, ExecActivityError };
|
|
73
|
+
export { CollationError, DurableTimeoutError, DurableMaxedError, DurableFatalError, DurableRetryError, DurableWaitForSignalError, DurableIncompleteSignalError, DurableSleepError, DurableSleepForError, DuplicateJobError, GetStateError, SetStateError, MapDataError, RegisterTimeoutError, ExecActivityError };
|
package/build/modules/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ExecActivityError = exports.RegisterTimeoutError = exports.MapDataError = exports.SetStateError = exports.GetStateError = exports.DuplicateJobError = exports.DurableSleepError = exports.DurableIncompleteSignalError = exports.DurableWaitForSignalError = exports.DurableRetryError = exports.DurableFatalError = exports.DurableMaxedError = exports.DurableTimeoutError = exports.CollationError = void 0;
|
|
3
|
+
exports.ExecActivityError = exports.RegisterTimeoutError = exports.MapDataError = exports.SetStateError = exports.GetStateError = exports.DuplicateJobError = exports.DurableSleepForError = exports.DurableSleepError = exports.DurableIncompleteSignalError = exports.DurableWaitForSignalError = exports.DurableRetryError = exports.DurableFatalError = exports.DurableMaxedError = exports.DurableTimeoutError = exports.CollationError = void 0;
|
|
4
4
|
class GetStateError extends Error {
|
|
5
5
|
constructor() {
|
|
6
6
|
super("Error occurred while getting job state");
|
|
@@ -31,6 +31,7 @@ class DurableWaitForSignalError extends Error {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
exports.DurableWaitForSignalError = DurableWaitForSignalError;
|
|
34
|
+
/* @deprecated */
|
|
34
35
|
class DurableSleepError extends Error {
|
|
35
36
|
constructor(message, duration, index, dimension) {
|
|
36
37
|
super(message);
|
|
@@ -41,6 +42,16 @@ class DurableSleepError extends Error {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
exports.DurableSleepError = DurableSleepError;
|
|
45
|
+
class DurableSleepForError extends Error {
|
|
46
|
+
constructor(message, duration, index, dimension) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.duration = duration;
|
|
49
|
+
this.index = index;
|
|
50
|
+
this.dimension = dimension;
|
|
51
|
+
this.code = 592;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.DurableSleepForError = DurableSleepForError;
|
|
44
55
|
class DurableTimeoutError extends Error {
|
|
45
56
|
constructor(message) {
|
|
46
57
|
super(message);
|
package/build/package.json
CHANGED
|
@@ -93,13 +93,12 @@ class ClientService {
|
|
|
93
93
|
};
|
|
94
94
|
const context = { metadata: { trc, spn }, data: {} };
|
|
95
95
|
const jobId = await hotMeshClient.pub(`${options.namespace ?? factory_1.APP_ID}.execute`, payload, context);
|
|
96
|
-
//
|
|
96
|
+
// Seed search data if present
|
|
97
97
|
if (jobId && options.search?.data) {
|
|
98
98
|
const searchSessionId = `-search-0`;
|
|
99
99
|
const search = new search_1.Search(jobId, hotMeshClient, searchSessionId);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
100
|
+
const entries = Object.entries(options.search.data).flat();
|
|
101
|
+
search.set(...entries);
|
|
103
102
|
}
|
|
104
103
|
return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
|
|
105
104
|
},
|
|
@@ -9,7 +9,8 @@ exports.DEFAULT_COEFFICIENT = exports.APP_ID = exports.APP_VERSION = exports.get
|
|
|
9
9
|
*
|
|
10
10
|
* ERROR CODES:
|
|
11
11
|
* 594: waitforsignal
|
|
12
|
-
*
|
|
12
|
+
* 592: sleepFor
|
|
13
|
+
* 595: sleep (deprecated)
|
|
13
14
|
* 596, 597, 598: fatal
|
|
14
15
|
* 599: retry
|
|
15
16
|
*/
|
|
@@ -129,6 +130,20 @@ const getWorkflowYAML = (app, version) => {
|
|
|
129
130
|
maps:
|
|
130
131
|
duration: '{$self.output.data.duration}'
|
|
131
132
|
index: '{$self.output.data.index}'
|
|
133
|
+
592:
|
|
134
|
+
schema:
|
|
135
|
+
type: object
|
|
136
|
+
properties:
|
|
137
|
+
duration:
|
|
138
|
+
type: number
|
|
139
|
+
description: sleepFor duration in seconds
|
|
140
|
+
index:
|
|
141
|
+
type: number
|
|
142
|
+
description: the current index
|
|
143
|
+
maps:
|
|
144
|
+
duration: '{$self.output.data.duration}'
|
|
145
|
+
index: '{$self.output.data.index}'
|
|
146
|
+
|
|
132
147
|
job:
|
|
133
148
|
maps:
|
|
134
149
|
response: '{$self.output.data.response}'
|
|
@@ -231,7 +246,20 @@ const getWorkflowYAML = (app, version) => {
|
|
|
231
246
|
maps:
|
|
232
247
|
duration: '{$self.output.data.duration}'
|
|
233
248
|
index: '{$self.output.data.index}'
|
|
234
|
-
|
|
249
|
+
592:
|
|
250
|
+
schema:
|
|
251
|
+
type: object
|
|
252
|
+
properties:
|
|
253
|
+
duration:
|
|
254
|
+
type: number
|
|
255
|
+
description: sleepFor duration in seconds
|
|
256
|
+
index:
|
|
257
|
+
type: number
|
|
258
|
+
description: the current index
|
|
259
|
+
maps:
|
|
260
|
+
duration: '{$self.output.data.duration}'
|
|
261
|
+
index: '{$self.output.data.index}'
|
|
262
|
+
|
|
235
263
|
siga594:
|
|
236
264
|
title: Signal In - Wait for signals
|
|
237
265
|
type: await
|
|
@@ -334,6 +362,19 @@ const getWorkflowYAML = (app, version) => {
|
|
|
334
362
|
maps:
|
|
335
363
|
duration: '{siga1.output.data.duration}'
|
|
336
364
|
|
|
365
|
+
siga592:
|
|
366
|
+
title: Signal In - Sleep For a duration and then cycle/goto
|
|
367
|
+
type: hook
|
|
368
|
+
sleep: '{sigw1.output.data.duration}'
|
|
369
|
+
|
|
370
|
+
sigc592:
|
|
371
|
+
title: Signal In - Goto Activity a1 after sleeping for a duration
|
|
372
|
+
type: cycle
|
|
373
|
+
ancestor: siga1
|
|
374
|
+
input:
|
|
375
|
+
maps:
|
|
376
|
+
duration: '{siga1.output.data.duration}'
|
|
377
|
+
|
|
337
378
|
siga599:
|
|
338
379
|
title: Signal In - Sleep exponentially longer and retry
|
|
339
380
|
type: hook
|
|
@@ -452,6 +493,19 @@ const getWorkflowYAML = (app, version) => {
|
|
|
452
493
|
maps:
|
|
453
494
|
duration: '{a1.output.data.duration}'
|
|
454
495
|
|
|
496
|
+
a592:
|
|
497
|
+
title: Sleep For a duration and then cycle/goto
|
|
498
|
+
type: hook
|
|
499
|
+
sleep: '{w1.output.data.duration}'
|
|
500
|
+
|
|
501
|
+
c592:
|
|
502
|
+
title: Goto Activity a1 after sleeping for a duration
|
|
503
|
+
type: cycle
|
|
504
|
+
ancestor: a1
|
|
505
|
+
input:
|
|
506
|
+
maps:
|
|
507
|
+
duration: '{a1.output.data.duration}'
|
|
508
|
+
|
|
455
509
|
a599:
|
|
456
510
|
title: Sleep exponentially longer before retrying
|
|
457
511
|
type: hook
|
|
@@ -655,6 +709,9 @@ const getWorkflowYAML = (app, version) => {
|
|
|
655
709
|
- to: siga595
|
|
656
710
|
conditions:
|
|
657
711
|
code: 595
|
|
712
|
+
- to: siga592
|
|
713
|
+
conditions:
|
|
714
|
+
code: 592
|
|
658
715
|
- to: siga599
|
|
659
716
|
conditions:
|
|
660
717
|
code: 599
|
|
@@ -666,6 +723,8 @@ const getWorkflowYAML = (app, version) => {
|
|
|
666
723
|
- to: sigc595
|
|
667
724
|
conditions:
|
|
668
725
|
code: 202
|
|
726
|
+
siga592:
|
|
727
|
+
- to: sigc592
|
|
669
728
|
siga599:
|
|
670
729
|
- to: sigc599
|
|
671
730
|
a1:
|
|
@@ -677,6 +736,9 @@ const getWorkflowYAML = (app, version) => {
|
|
|
677
736
|
- to: a595
|
|
678
737
|
conditions:
|
|
679
738
|
code: 595
|
|
739
|
+
- to: a592
|
|
740
|
+
conditions:
|
|
741
|
+
code: 592
|
|
680
742
|
- to: a599
|
|
681
743
|
conditions:
|
|
682
744
|
code: 599
|
|
@@ -703,6 +765,8 @@ const getWorkflowYAML = (app, version) => {
|
|
|
703
765
|
- to: c595
|
|
704
766
|
conditions:
|
|
705
767
|
code: 202
|
|
768
|
+
a592:
|
|
769
|
+
- to: c592
|
|
706
770
|
a599:
|
|
707
771
|
- to: c599
|
|
708
772
|
|
|
@@ -178,7 +178,22 @@ class WorkerService {
|
|
|
178
178
|
}
|
|
179
179
|
catch (err) {
|
|
180
180
|
//not an error...just a trigger to sleep
|
|
181
|
-
if (err instanceof errors_1.
|
|
181
|
+
if (err instanceof errors_1.DurableSleepForError) {
|
|
182
|
+
return {
|
|
183
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
184
|
+
code: err.code,
|
|
185
|
+
metadata: { ...data.metadata },
|
|
186
|
+
data: {
|
|
187
|
+
code: err.code,
|
|
188
|
+
message: JSON.stringify({ duration: err.duration, index: err.index, dimension: err.dimension }),
|
|
189
|
+
duration: err.duration,
|
|
190
|
+
index: err.index,
|
|
191
|
+
dimension: err.dimension
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
//deprecated format; not an error...just a trigger to sleep
|
|
195
|
+
}
|
|
196
|
+
else if (err instanceof errors_1.DurableSleepError) {
|
|
182
197
|
return {
|
|
183
198
|
status: stream_1.StreamStatus.SUCCESS,
|
|
184
199
|
code: err.code,
|
|
@@ -81,10 +81,21 @@ export declare class WorkflowService {
|
|
|
81
81
|
*/
|
|
82
82
|
static hook(options: HookOptions): Promise<string>;
|
|
83
83
|
/**
|
|
84
|
-
* Sleeps for a duration.
|
|
84
|
+
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
85
|
+
* upon reentry, the function will traverse prior execution paths up
|
|
86
|
+
* until the sleep command and then resume execution from that point.
|
|
85
87
|
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
86
88
|
* @returns {Promise<number>}
|
|
87
89
|
*/
|
|
90
|
+
static sleepFor(duration: string): Promise<number>;
|
|
91
|
+
/**
|
|
92
|
+
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
93
|
+
* upon reentry, the function will traverse prior execution paths up
|
|
94
|
+
* until the sleep command and then resume execution from that point.
|
|
95
|
+
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
96
|
+
* @returns {Promise<number>}
|
|
97
|
+
* @deprecated - use `sleepFor` instead
|
|
98
|
+
*/
|
|
88
99
|
static sleep(duration: string): Promise<number>;
|
|
89
100
|
/**
|
|
90
101
|
* Waits for a signal to awaken
|
|
@@ -262,10 +262,37 @@ class WorkflowService {
|
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
/**
|
|
265
|
-
* Sleeps for a duration.
|
|
265
|
+
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
266
|
+
* upon reentry, the function will traverse prior execution paths up
|
|
267
|
+
* until the sleep command and then resume execution from that point.
|
|
266
268
|
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
267
269
|
* @returns {Promise<number>}
|
|
268
270
|
*/
|
|
271
|
+
static async sleepFor(duration) {
|
|
272
|
+
const seconds = (0, ms_1.default)(duration) / 1000;
|
|
273
|
+
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
274
|
+
const namespace = store.get('namespace');
|
|
275
|
+
const workflowTopic = store.get('workflowTopic');
|
|
276
|
+
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
277
|
+
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'sleep')) {
|
|
278
|
+
const workflowId = store.get('workflowId');
|
|
279
|
+
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
280
|
+
const COUNTER = store.get('counter');
|
|
281
|
+
const execIndex = COUNTER.counter;
|
|
282
|
+
// spawn a new sleep job if error code 592 is thrown by the worker
|
|
283
|
+
// NOTE: If this message appears in the stack trace, the `.sleepFor()` method in your workflow code was NOT awaited.
|
|
284
|
+
throw new errors_1.DurableSleepForError(workflowId, seconds, execIndex, workflowDimension);
|
|
285
|
+
}
|
|
286
|
+
return seconds;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
290
|
+
* upon reentry, the function will traverse prior execution paths up
|
|
291
|
+
* until the sleep command and then resume execution from that point.
|
|
292
|
+
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
293
|
+
* @returns {Promise<number>}
|
|
294
|
+
* @deprecated - use `sleepFor` instead
|
|
295
|
+
*/
|
|
269
296
|
static async sleep(duration) {
|
|
270
297
|
const seconds = (0, ms_1.default)(duration) / 1000;
|
|
271
298
|
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
@@ -284,7 +311,6 @@ class WorkflowService {
|
|
|
284
311
|
}
|
|
285
312
|
catch (e) {
|
|
286
313
|
// spawn a new sleep job if error code 595 is thrown by the worker)
|
|
287
|
-
// NOTE: If this message shows up in your stack trace, you forgot to await `Durable.workflow.sleep()` in your workflow code.
|
|
288
314
|
throw new errors_1.DurableSleepError(workflowId, seconds, execIndex, workflowDimension);
|
|
289
315
|
}
|
|
290
316
|
}
|
package/modules/errors.ts
CHANGED
|
@@ -33,6 +33,7 @@ class DurableWaitForSignalError extends Error {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/* @deprecated */
|
|
36
37
|
class DurableSleepError extends Error {
|
|
37
38
|
code: number;
|
|
38
39
|
duration: number; //seconds
|
|
@@ -46,6 +47,19 @@ class DurableSleepError extends Error {
|
|
|
46
47
|
this.code = 595;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
class DurableSleepForError extends Error {
|
|
51
|
+
code: number;
|
|
52
|
+
duration: number; //seconds
|
|
53
|
+
index: number; //execution order in the workflow
|
|
54
|
+
dimension: string; //hook dimension (e.g., ',0,1,0') (uses empty string for `null`)
|
|
55
|
+
constructor(message: string, duration: number, index: number, dimension: string) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.duration = duration;
|
|
58
|
+
this.index = index;
|
|
59
|
+
this.dimension = dimension;
|
|
60
|
+
this.code = 592;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
49
63
|
class DurableTimeoutError extends Error {
|
|
50
64
|
code: number;
|
|
51
65
|
constructor(message: string) {
|
|
@@ -124,6 +138,7 @@ export {
|
|
|
124
138
|
DurableWaitForSignalError,
|
|
125
139
|
DurableIncompleteSignalError,
|
|
126
140
|
DurableSleepError,
|
|
141
|
+
DurableSleepForError,
|
|
127
142
|
DuplicateJobError,
|
|
128
143
|
GetStateError,
|
|
129
144
|
SetStateError,
|
package/package.json
CHANGED
|
@@ -113,13 +113,12 @@ export class ClientService {
|
|
|
113
113
|
payload,
|
|
114
114
|
context as JobState
|
|
115
115
|
);
|
|
116
|
-
//
|
|
116
|
+
// Seed search data if present
|
|
117
117
|
if (jobId && options.search?.data) {
|
|
118
118
|
const searchSessionId = `-search-0`;
|
|
119
119
|
const search = new Search(jobId, hotMeshClient, searchSessionId);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
120
|
+
const entries = Object.entries(options.search.data).flat();
|
|
121
|
+
search.set(...entries);
|
|
123
122
|
}
|
|
124
123
|
return new WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
|
|
125
124
|
},
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* ERROR CODES:
|
|
8
8
|
* 594: waitforsignal
|
|
9
|
-
*
|
|
9
|
+
* 592: sleepFor
|
|
10
|
+
* 595: sleep (deprecated)
|
|
10
11
|
* 596, 597, 598: fatal
|
|
11
12
|
* 599: retry
|
|
12
13
|
*/
|
|
@@ -126,6 +127,20 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
126
127
|
maps:
|
|
127
128
|
duration: '{$self.output.data.duration}'
|
|
128
129
|
index: '{$self.output.data.index}'
|
|
130
|
+
592:
|
|
131
|
+
schema:
|
|
132
|
+
type: object
|
|
133
|
+
properties:
|
|
134
|
+
duration:
|
|
135
|
+
type: number
|
|
136
|
+
description: sleepFor duration in seconds
|
|
137
|
+
index:
|
|
138
|
+
type: number
|
|
139
|
+
description: the current index
|
|
140
|
+
maps:
|
|
141
|
+
duration: '{$self.output.data.duration}'
|
|
142
|
+
index: '{$self.output.data.index}'
|
|
143
|
+
|
|
129
144
|
job:
|
|
130
145
|
maps:
|
|
131
146
|
response: '{$self.output.data.response}'
|
|
@@ -228,7 +243,20 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
228
243
|
maps:
|
|
229
244
|
duration: '{$self.output.data.duration}'
|
|
230
245
|
index: '{$self.output.data.index}'
|
|
231
|
-
|
|
246
|
+
592:
|
|
247
|
+
schema:
|
|
248
|
+
type: object
|
|
249
|
+
properties:
|
|
250
|
+
duration:
|
|
251
|
+
type: number
|
|
252
|
+
description: sleepFor duration in seconds
|
|
253
|
+
index:
|
|
254
|
+
type: number
|
|
255
|
+
description: the current index
|
|
256
|
+
maps:
|
|
257
|
+
duration: '{$self.output.data.duration}'
|
|
258
|
+
index: '{$self.output.data.index}'
|
|
259
|
+
|
|
232
260
|
siga594:
|
|
233
261
|
title: Signal In - Wait for signals
|
|
234
262
|
type: await
|
|
@@ -331,6 +359,19 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
331
359
|
maps:
|
|
332
360
|
duration: '{siga1.output.data.duration}'
|
|
333
361
|
|
|
362
|
+
siga592:
|
|
363
|
+
title: Signal In - Sleep For a duration and then cycle/goto
|
|
364
|
+
type: hook
|
|
365
|
+
sleep: '{sigw1.output.data.duration}'
|
|
366
|
+
|
|
367
|
+
sigc592:
|
|
368
|
+
title: Signal In - Goto Activity a1 after sleeping for a duration
|
|
369
|
+
type: cycle
|
|
370
|
+
ancestor: siga1
|
|
371
|
+
input:
|
|
372
|
+
maps:
|
|
373
|
+
duration: '{siga1.output.data.duration}'
|
|
374
|
+
|
|
334
375
|
siga599:
|
|
335
376
|
title: Signal In - Sleep exponentially longer and retry
|
|
336
377
|
type: hook
|
|
@@ -449,6 +490,19 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
449
490
|
maps:
|
|
450
491
|
duration: '{a1.output.data.duration}'
|
|
451
492
|
|
|
493
|
+
a592:
|
|
494
|
+
title: Sleep For a duration and then cycle/goto
|
|
495
|
+
type: hook
|
|
496
|
+
sleep: '{w1.output.data.duration}'
|
|
497
|
+
|
|
498
|
+
c592:
|
|
499
|
+
title: Goto Activity a1 after sleeping for a duration
|
|
500
|
+
type: cycle
|
|
501
|
+
ancestor: a1
|
|
502
|
+
input:
|
|
503
|
+
maps:
|
|
504
|
+
duration: '{a1.output.data.duration}'
|
|
505
|
+
|
|
452
506
|
a599:
|
|
453
507
|
title: Sleep exponentially longer before retrying
|
|
454
508
|
type: hook
|
|
@@ -652,6 +706,9 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
652
706
|
- to: siga595
|
|
653
707
|
conditions:
|
|
654
708
|
code: 595
|
|
709
|
+
- to: siga592
|
|
710
|
+
conditions:
|
|
711
|
+
code: 592
|
|
655
712
|
- to: siga599
|
|
656
713
|
conditions:
|
|
657
714
|
code: 599
|
|
@@ -663,6 +720,8 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
663
720
|
- to: sigc595
|
|
664
721
|
conditions:
|
|
665
722
|
code: 202
|
|
723
|
+
siga592:
|
|
724
|
+
- to: sigc592
|
|
666
725
|
siga599:
|
|
667
726
|
- to: sigc599
|
|
668
727
|
a1:
|
|
@@ -674,6 +733,9 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
674
733
|
- to: a595
|
|
675
734
|
conditions:
|
|
676
735
|
code: 595
|
|
736
|
+
- to: a592
|
|
737
|
+
conditions:
|
|
738
|
+
code: 592
|
|
677
739
|
- to: a599
|
|
678
740
|
conditions:
|
|
679
741
|
code: 599
|
|
@@ -700,6 +762,8 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
700
762
|
- to: c595
|
|
701
763
|
conditions:
|
|
702
764
|
code: 202
|
|
765
|
+
a592:
|
|
766
|
+
- to: c592
|
|
703
767
|
a599:
|
|
704
768
|
- to: c599
|
|
705
769
|
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
DurableMaxedError,
|
|
5
5
|
DurableRetryError,
|
|
6
6
|
DurableSleepError,
|
|
7
|
+
DurableSleepForError,
|
|
7
8
|
DurableTimeoutError,
|
|
8
9
|
DurableWaitForSignalError} from '../../modules/errors';
|
|
9
10
|
import { asyncLocalStorage } from './asyncLocalStorage';
|
|
@@ -226,7 +227,22 @@ export class WorkerService {
|
|
|
226
227
|
} catch (err) {
|
|
227
228
|
|
|
228
229
|
//not an error...just a trigger to sleep
|
|
229
|
-
if (err instanceof
|
|
230
|
+
if (err instanceof DurableSleepForError) {
|
|
231
|
+
return {
|
|
232
|
+
status: StreamStatus.SUCCESS,
|
|
233
|
+
code: err.code,
|
|
234
|
+
metadata: { ...data.metadata },
|
|
235
|
+
data: {
|
|
236
|
+
code: err.code,
|
|
237
|
+
message: JSON.stringify({ duration: err.duration, index: err.index, dimension: err.dimension }),
|
|
238
|
+
duration: err.duration,
|
|
239
|
+
index: err.index,
|
|
240
|
+
dimension: err.dimension
|
|
241
|
+
}
|
|
242
|
+
} as StreamDataResponse;
|
|
243
|
+
|
|
244
|
+
//deprecated format; not an error...just a trigger to sleep
|
|
245
|
+
} else if (err instanceof DurableSleepError) {
|
|
230
246
|
return {
|
|
231
247
|
status: StreamStatus.SUCCESS,
|
|
232
248
|
code: err.code,
|
|
@@ -3,6 +3,7 @@ import ms from 'ms';
|
|
|
3
3
|
import {
|
|
4
4
|
DurableIncompleteSignalError,
|
|
5
5
|
DurableSleepError,
|
|
6
|
+
DurableSleepForError,
|
|
6
7
|
DurableWaitForSignalError } from '../../modules/errors';
|
|
7
8
|
import { KeyService, KeyType } from '../../modules/key';
|
|
8
9
|
import { asyncLocalStorage } from './asyncLocalStorage';
|
|
@@ -289,10 +290,38 @@ export class WorkflowService {
|
|
|
289
290
|
}
|
|
290
291
|
|
|
291
292
|
/**
|
|
292
|
-
* Sleeps for a duration.
|
|
293
|
+
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
294
|
+
* upon reentry, the function will traverse prior execution paths up
|
|
295
|
+
* until the sleep command and then resume execution from that point.
|
|
293
296
|
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
294
297
|
* @returns {Promise<number>}
|
|
295
298
|
*/
|
|
299
|
+
static async sleepFor(duration: string): Promise<number> {
|
|
300
|
+
const seconds = ms(duration) / 1000;
|
|
301
|
+
const store = asyncLocalStorage.getStore();
|
|
302
|
+
const namespace = store.get('namespace');
|
|
303
|
+
const workflowTopic = store.get('workflowTopic');
|
|
304
|
+
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
305
|
+
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'sleep')) {
|
|
306
|
+
const workflowId = store.get('workflowId');
|
|
307
|
+
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
308
|
+
const COUNTER = store.get('counter');
|
|
309
|
+
const execIndex = COUNTER.counter;
|
|
310
|
+
// spawn a new sleep job if error code 592 is thrown by the worker
|
|
311
|
+
// NOTE: If this message appears in the stack trace, the `.sleepFor()` method in your workflow code was NOT awaited.
|
|
312
|
+
throw new DurableSleepForError(workflowId, seconds, execIndex, workflowDimension);
|
|
313
|
+
}
|
|
314
|
+
return seconds;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
319
|
+
* upon reentry, the function will traverse prior execution paths up
|
|
320
|
+
* until the sleep command and then resume execution from that point.
|
|
321
|
+
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
322
|
+
* @returns {Promise<number>}
|
|
323
|
+
* @deprecated - use `sleepFor` instead
|
|
324
|
+
*/
|
|
296
325
|
static async sleep(duration: string): Promise<number> {
|
|
297
326
|
const seconds = ms(duration) / 1000;
|
|
298
327
|
|
|
@@ -312,7 +341,6 @@ export class WorkflowService {
|
|
|
312
341
|
return seconds;
|
|
313
342
|
} catch (e) {
|
|
314
343
|
// spawn a new sleep job if error code 595 is thrown by the worker)
|
|
315
|
-
// NOTE: If this message shows up in your stack trace, you forgot to await `Durable.workflow.sleep()` in your workflow code.
|
|
316
344
|
throw new DurableSleepError(workflowId, seconds, execIndex, workflowDimension);
|
|
317
345
|
}
|
|
318
346
|
}
|