@hotmeshio/hotmesh 0.0.52 → 0.0.54
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 +22 -18
- package/build/index.d.ts +1 -2
- package/build/index.js +1 -3
- package/build/modules/enums.d.ts +8 -3
- package/build/modules/enums.js +16 -8
- package/build/modules/errors.d.ts +58 -20
- package/build/modules/errors.js +90 -33
- package/build/package.json +7 -2
- package/build/services/activities/activity.d.ts +8 -0
- package/build/services/activities/activity.js +63 -14
- package/build/services/activities/await.js +6 -6
- package/build/services/activities/cycle.d.ts +2 -2
- package/build/services/activities/cycle.js +5 -5
- package/build/services/activities/hook.js +9 -5
- package/build/services/activities/interrupt.d.ts +3 -3
- package/build/services/activities/interrupt.js +15 -6
- package/build/services/activities/signal.d.ts +2 -2
- package/build/services/activities/signal.js +4 -4
- package/build/services/activities/trigger.d.ts +5 -2
- package/build/services/activities/trigger.js +34 -4
- package/build/services/activities/worker.js +6 -6
- package/build/services/compiler/deployer.js +33 -5
- package/build/services/compiler/validator.d.ts +2 -0
- package/build/services/compiler/validator.js +5 -1
- package/build/services/durable/client.d.ts +7 -1
- package/build/services/durable/client.js +57 -38
- package/build/services/durable/exporter.d.ts +27 -81
- package/build/services/durable/exporter.js +153 -325
- package/build/services/durable/handle.d.ts +13 -8
- package/build/services/durable/handle.js +61 -48
- package/build/services/durable/index.d.ts +0 -2
- package/build/services/durable/index.js +0 -2
- package/build/services/durable/schemas/factory.d.ts +33 -0
- package/build/services/durable/schemas/factory.js +2356 -0
- package/build/services/durable/search.js +8 -8
- package/build/services/durable/worker.js +117 -25
- package/build/services/durable/workflow.d.ts +67 -52
- package/build/services/durable/workflow.js +322 -306
- package/build/services/engine/index.d.ts +2 -2
- package/build/services/engine/index.js +5 -2
- package/build/services/exporter/index.d.ts +2 -4
- package/build/services/exporter/index.js +4 -5
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +2 -2
- package/build/services/mapper/index.d.ts +6 -2
- package/build/services/mapper/index.js +6 -2
- package/build/services/pipe/functions/array.d.ts +2 -10
- package/build/services/pipe/functions/array.js +30 -28
- package/build/services/pipe/functions/conditional.d.ts +1 -0
- package/build/services/pipe/functions/conditional.js +3 -0
- package/build/services/pipe/functions/date.d.ts +1 -0
- package/build/services/pipe/functions/date.js +4 -0
- package/build/services/pipe/functions/index.d.ts +2 -0
- package/build/services/pipe/functions/index.js +2 -0
- package/build/services/pipe/functions/logical.d.ts +5 -0
- package/build/services/pipe/functions/logical.js +12 -0
- package/build/services/pipe/functions/object.d.ts +3 -0
- package/build/services/pipe/functions/object.js +25 -7
- package/build/services/pipe/index.d.ts +20 -3
- package/build/services/pipe/index.js +82 -16
- package/build/services/router/index.js +14 -3
- package/build/services/serializer/index.d.ts +3 -2
- package/build/services/serializer/index.js +11 -4
- package/build/services/store/clients/ioredis.js +6 -6
- package/build/services/store/clients/redis.js +7 -7
- package/build/services/store/index.d.ts +2 -0
- package/build/services/store/index.js +4 -1
- package/build/services/stream/clients/ioredis.js +8 -8
- package/build/services/stream/clients/redis.js +1 -1
- package/build/types/activity.d.ts +60 -5
- package/build/types/durable.d.ts +183 -36
- package/build/types/error.d.ts +48 -0
- package/build/types/error.js +2 -0
- package/build/types/exporter.d.ts +35 -7
- package/build/types/index.d.ts +4 -3
- package/build/types/job.d.ts +93 -6
- package/build/types/pipe.d.ts +81 -3
- package/build/types/stream.d.ts +61 -1
- package/build/types/stream.js +4 -0
- package/index.ts +1 -2
- package/modules/enums.ts +16 -8
- package/modules/errors.ts +139 -34
- package/package.json +7 -2
- package/services/activities/activity.ts +63 -14
- package/services/activities/await.ts +6 -6
- package/services/activities/cycle.ts +7 -6
- package/services/activities/hook.ts +12 -5
- package/services/activities/interrupt.ts +19 -9
- package/services/activities/signal.ts +6 -5
- package/services/activities/trigger.ts +43 -6
- package/services/activities/worker.ts +7 -7
- package/services/compiler/deployer.ts +33 -6
- package/services/compiler/validator.ts +7 -3
- package/services/durable/client.ts +49 -22
- package/services/durable/exporter.ts +162 -349
- package/services/durable/handle.ts +66 -53
- package/services/durable/index.ts +0 -2
- package/services/durable/schemas/factory.ts +2358 -0
- package/services/durable/search.ts +8 -8
- package/services/durable/worker.ts +128 -29
- package/services/durable/workflow.ts +371 -322
- package/services/engine/index.ts +8 -3
- package/services/exporter/index.ts +10 -12
- package/services/hotmesh/index.ts +4 -3
- package/services/mapper/index.ts +6 -2
- package/services/pipe/functions/array.ts +24 -37
- package/services/pipe/functions/conditional.ts +4 -0
- package/services/pipe/functions/date.ts +6 -0
- package/services/pipe/functions/index.ts +7 -5
- package/services/pipe/functions/logical.ts +11 -0
- package/services/pipe/functions/object.ts +26 -7
- package/services/pipe/index.ts +99 -21
- package/services/quorum/index.ts +1 -3
- package/services/router/index.ts +14 -3
- package/services/serializer/index.ts +12 -5
- package/services/store/clients/ioredis.ts +6 -6
- package/services/store/clients/redis.ts +7 -7
- package/services/store/index.ts +4 -1
- package/services/stream/clients/ioredis.ts +8 -8
- package/services/stream/clients/redis.ts +1 -1
- package/types/activity.ts +87 -15
- package/types/durable.ts +263 -75
- package/types/error.ts +52 -0
- package/types/exporter.ts +43 -9
- package/types/index.ts +14 -8
- package/types/job.ts +157 -36
- package/types/pipe.ts +84 -3
- package/types/stream.ts +82 -23
- package/build/services/durable/factory.d.ts +0 -17
- package/build/services/durable/factory.js +0 -817
- package/build/services/durable/meshos.d.ts +0 -127
- package/build/services/durable/meshos.js +0 -380
- package/services/durable/factory.ts +0 -818
- package/services/durable/meshos.ts +0 -441
|
@@ -8,117 +8,211 @@ const ms_1 = __importDefault(require("ms"));
|
|
|
8
8
|
const errors_1 = require("../../modules/errors");
|
|
9
9
|
const key_1 = require("../../modules/key");
|
|
10
10
|
const storage_1 = require("../../modules/storage");
|
|
11
|
-
const
|
|
12
|
-
const connection_1 = require("./connection");
|
|
13
|
-
const factory_1 = require("./factory");
|
|
11
|
+
const utils_1 = require("../../modules/utils");
|
|
14
12
|
const search_1 = require("./search");
|
|
15
13
|
const worker_1 = require("./worker");
|
|
14
|
+
const serializer_1 = require("../serializer");
|
|
16
15
|
const stream_1 = require("../../types/stream");
|
|
17
|
-
const
|
|
16
|
+
const enums_1 = require("../../modules/enums");
|
|
18
17
|
class WorkflowService {
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @
|
|
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
|
+
*/
|
|
25
|
+
static async didRun(prefix) {
|
|
26
|
+
const { COUNTER, replay, workflowDimension, } = WorkflowService.getContext();
|
|
27
|
+
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
28
|
+
const sessionId = `-${prefix}${workflowDimension}-${execIndex}-`;
|
|
29
|
+
if (sessionId in replay) {
|
|
30
|
+
const restored = serializer_1.SerializerService.fromString(replay[sessionId]);
|
|
31
|
+
return [true, execIndex, restored];
|
|
32
|
+
}
|
|
33
|
+
return [false, execIndex, null];
|
|
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
|
|
24
40
|
*/
|
|
25
|
-
static async
|
|
41
|
+
static async isSideEffectAllowed(hotMeshClient, prefix) {
|
|
26
42
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
27
|
-
const namespace = store.get('namespace');
|
|
28
43
|
const workflowId = store.get('workflowId');
|
|
29
|
-
const originJobId = store.get('originJobId');
|
|
30
44
|
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
31
|
-
const workflowTrace = store.get('workflowTrace');
|
|
32
|
-
const workflowSpan = store.get('workflowSpan');
|
|
33
45
|
const COUNTER = store.get('counter');
|
|
34
46
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const childJobId = options.workflowId ?? `${entityOrEmptyString}-${workflowId}-$${options.entity ?? options.workflowName}${workflowDimension}-${execIndex}`;
|
|
40
|
-
const parentWorkflowId = workflowId;
|
|
41
|
-
const client = new client_1.ClientService({
|
|
42
|
-
connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
|
|
43
|
-
});
|
|
44
|
-
let handle = await client.workflow.getHandle(options.entity ?? options.taskQueue, options.entity ?? options.workflowName, childJobId, namespace);
|
|
45
|
-
try {
|
|
46
|
-
return await handle.result(true);
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
handle = await client.workflow.start({
|
|
50
|
-
...options,
|
|
51
|
-
namespace,
|
|
52
|
-
workflowId: childJobId,
|
|
53
|
-
originJobId: originJobId ?? workflowId,
|
|
54
|
-
parentWorkflowId,
|
|
55
|
-
workflowTrace,
|
|
56
|
-
workflowSpan,
|
|
57
|
-
});
|
|
58
|
-
//todo: options.startToCloseTimeout
|
|
59
|
-
const result = await handle.result();
|
|
60
|
-
return result;
|
|
47
|
+
const sessionId = `-${prefix}${workflowDimension}-${execIndex}-`;
|
|
48
|
+
const replay = store.get('replay');
|
|
49
|
+
if (sessionId in replay) {
|
|
50
|
+
return false;
|
|
61
51
|
}
|
|
52
|
+
const keyParams = {
|
|
53
|
+
appId: hotMeshClient.appId,
|
|
54
|
+
jobId: workflowId
|
|
55
|
+
};
|
|
56
|
+
const workflowGuid = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
|
|
57
|
+
const guidValue = Number(await hotMeshClient.engine.store.exec('HINCRBYFLOAT', workflowGuid, sessionId, '1'));
|
|
58
|
+
return guidValue === 1;
|
|
62
59
|
}
|
|
63
60
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* @param {WorkflowOptions} options - the workflow options
|
|
67
|
-
* @returns {Promise<string>} - the childJobId
|
|
61
|
+
* Returns the current workflow context restored
|
|
62
|
+
* from Redis
|
|
68
63
|
*/
|
|
69
|
-
static
|
|
64
|
+
static getContext() {
|
|
70
65
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
71
|
-
const namespace = store.get('namespace');
|
|
72
66
|
const workflowId = store.get('workflowId');
|
|
67
|
+
const replay = store.get('replay');
|
|
68
|
+
const cursor = store.get('cursor');
|
|
69
|
+
const interruptionRegistry = store.get('interruptionRegistry');
|
|
73
70
|
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
71
|
+
const workflowTopic = store.get('workflowTopic');
|
|
72
|
+
const namespace = store.get('namespace');
|
|
73
|
+
const originJobId = store.get('originJobId');
|
|
74
74
|
const workflowTrace = store.get('workflowTrace');
|
|
75
|
+
const canRetry = store.get('canRetry');
|
|
75
76
|
const workflowSpan = store.get('workflowSpan');
|
|
76
77
|
const COUNTER = store.get('counter');
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
const raw = store.get('raw');
|
|
79
|
+
return {
|
|
80
|
+
canRetry,
|
|
81
|
+
COUNTER,
|
|
82
|
+
counter: COUNTER.counter,
|
|
83
|
+
cursor,
|
|
84
|
+
interruptionRegistry,
|
|
85
|
+
namespace,
|
|
86
|
+
originJobId,
|
|
87
|
+
raw,
|
|
88
|
+
replay,
|
|
89
|
+
workflowId,
|
|
90
|
+
workflowDimension,
|
|
91
|
+
workflowTopic,
|
|
92
|
+
workflowTrace,
|
|
93
|
+
workflowSpan,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Return a handle to the hotmesh client hosting the workflow execution
|
|
98
|
+
* @returns {Promise<HotMesh>} - a hotmesh client
|
|
99
|
+
*/
|
|
100
|
+
static async getHotMesh() {
|
|
101
|
+
const store = storage_1.asyncLocalStorage.getStore();
|
|
102
|
+
const workflowTopic = store.get('workflowTopic');
|
|
103
|
+
const namespace = store.get('namespace');
|
|
104
|
+
return await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
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
|
+
*/
|
|
114
|
+
static async execChild(options) {
|
|
115
|
+
//SYNC
|
|
116
|
+
//check if the activity already ran (check $error/done)
|
|
117
|
+
const isStartChild = options.await === false;
|
|
118
|
+
const prefix = isStartChild ? 'start' : 'child';
|
|
119
|
+
const [didRun, execIndex, result] = await WorkflowService.didRun(prefix);
|
|
120
|
+
const context = this.getContext();
|
|
121
|
+
const { canRetry, interruptionRegistry } = context;
|
|
122
|
+
if (didRun) {
|
|
123
|
+
if (result?.$error && (!result.$error.is_stream_error || (result.$error.is_stream_error && !canRetry))) {
|
|
124
|
+
if (options?.config?.throwOnError !== false) {
|
|
125
|
+
//rethrow remote execution error (simulates local failure)
|
|
126
|
+
const code = result.$error.code;
|
|
127
|
+
const message = result.$error.message;
|
|
128
|
+
const stack = result.$error.stack;
|
|
129
|
+
if (code === enums_1.HMSH_CODE_DURABLE_FATAL) {
|
|
130
|
+
throw new errors_1.DurableFatalError(message, stack);
|
|
131
|
+
}
|
|
132
|
+
else if (code == enums_1.HMSH_CODE_DURABLE_MAXED) {
|
|
133
|
+
throw new errors_1.DurableMaxedError(message, stack);
|
|
134
|
+
}
|
|
135
|
+
else if (code == enums_1.HMSH_CODE_DURABLE_TIMEOUT) {
|
|
136
|
+
throw new errors_1.DurableTimeoutError(message, stack);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
throw new errors_1.DurableRetryError(message, stack);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return result.$error;
|
|
143
|
+
}
|
|
144
|
+
else if (result.data) {
|
|
145
|
+
return result.data;
|
|
146
|
+
}
|
|
82
147
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
148
|
+
const interruptionMessage = this.getChildInterruptPayload(context, options, execIndex);
|
|
149
|
+
//push the packaged inputs to the registry
|
|
150
|
+
interruptionRegistry.push({
|
|
151
|
+
code: enums_1.HMSH_CODE_DURABLE_CHILD,
|
|
152
|
+
...interruptionMessage,
|
|
153
|
+
});
|
|
154
|
+
//ASYNC
|
|
155
|
+
//sleep (allow others to be packaged / registered) and throw the error
|
|
156
|
+
await (0, utils_1.sleepFor)(0);
|
|
157
|
+
throw new errors_1.DurableChildError(interruptionMessage);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* constructs the payload necessary to spawn a child job
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
static getChildInterruptPayload(context, options, execIndex) {
|
|
164
|
+
const { workflowId, originJobId, workflowDimension } = context;
|
|
165
|
+
let childJobId;
|
|
166
|
+
if (options.workflowId) {
|
|
167
|
+
childJobId = options.workflowId;
|
|
168
|
+
}
|
|
169
|
+
else if (options.entity) {
|
|
170
|
+
childJobId = `${options.entity}-${workflowId.substring(0, 7)}-${(0, utils_1.guid)()}-${workflowDimension}-${execIndex}`;
|
|
94
171
|
}
|
|
95
172
|
else {
|
|
96
|
-
childJobId = options.
|
|
173
|
+
childJobId = `-${options.workflowName}-${(0, utils_1.guid)()}-${workflowDimension}-${execIndex}`;
|
|
97
174
|
}
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
175
|
+
const parentWorkflowId = workflowId;
|
|
176
|
+
const taskQueueName = options.entity ?? options.taskQueue;
|
|
177
|
+
const workflowName = options.entity ?? options.workflowName;
|
|
178
|
+
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
179
|
+
return {
|
|
180
|
+
arguments: [...(options.args || [])],
|
|
181
|
+
await: options?.await ?? true,
|
|
182
|
+
backoffCoefficient: options?.config?.backoffCoefficient ?? enums_1.HMSH_DURABLE_EXP_BACKOFF,
|
|
183
|
+
index: execIndex,
|
|
184
|
+
maximumAttempts: options?.config?.maximumAttempts ?? enums_1.HMSH_DURABLE_MAX_ATTEMPTS,
|
|
185
|
+
maximumInterval: (0, ms_1.default)(options?.config?.maximumInterval ?? enums_1.HMSH_DURABLE_MAX_INTERVAL) / 1000,
|
|
186
|
+
originJobId: originJobId ?? workflowId,
|
|
105
187
|
parentWorkflowId,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return childJobId;
|
|
188
|
+
workflowDimension: workflowDimension,
|
|
189
|
+
workflowId: childJobId,
|
|
190
|
+
workflowTopic,
|
|
191
|
+
};
|
|
111
192
|
}
|
|
112
193
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
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
|
+
*/
|
|
204
|
+
static async startChild(options) {
|
|
205
|
+
return this.execChild({ ...options, await: false });
|
|
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
|
|
119
210
|
*
|
|
211
|
+
* @param {ActivityConfig} options - the activity configuration
|
|
212
|
+
* that will be used to wrap the activities.
|
|
120
213
|
* @returns {ProxyType<ACT>} - a proxy object with the same keys as the
|
|
121
214
|
* activities object, but with the values replaced by a wrapped function
|
|
215
|
+
*
|
|
122
216
|
* @example
|
|
123
217
|
* // import the activities
|
|
124
218
|
* import * as activities from './activities';
|
|
@@ -141,6 +235,73 @@ class WorkflowService {
|
|
|
141
235
|
}
|
|
142
236
|
return proxy;
|
|
143
237
|
}
|
|
238
|
+
static wrapActivity(activityName, options) {
|
|
239
|
+
return async function () {
|
|
240
|
+
//SYNC
|
|
241
|
+
//check if the activity already ran
|
|
242
|
+
const [didRun, execIndex, result] = await WorkflowService.didRun('proxy');
|
|
243
|
+
if (didRun) {
|
|
244
|
+
if (result?.$error) {
|
|
245
|
+
if (options?.retryPolicy?.throwOnError !== false) {
|
|
246
|
+
//rethrow remote execution error (simulates throw)
|
|
247
|
+
const code = result.$error.code;
|
|
248
|
+
const message = result.$error.message;
|
|
249
|
+
const stack = result.$error.stack;
|
|
250
|
+
if (code === enums_1.HMSH_CODE_DURABLE_FATAL) {
|
|
251
|
+
throw new errors_1.DurableFatalError(message, stack);
|
|
252
|
+
}
|
|
253
|
+
else if (code == enums_1.HMSH_CODE_DURABLE_MAXED) {
|
|
254
|
+
throw new errors_1.DurableMaxedError(message, stack);
|
|
255
|
+
}
|
|
256
|
+
else if (code == enums_1.HMSH_CODE_DURABLE_TIMEOUT) {
|
|
257
|
+
throw new errors_1.DurableTimeoutError(message, stack);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return result.$error;
|
|
261
|
+
}
|
|
262
|
+
return result.data;
|
|
263
|
+
}
|
|
264
|
+
//package the interruption inputs
|
|
265
|
+
const context = WorkflowService.getContext();
|
|
266
|
+
const { interruptionRegistry } = context;
|
|
267
|
+
const interruptionMessage = WorkflowService.getProxyInterruptPayload(context, activityName, execIndex, Array.from(arguments), options);
|
|
268
|
+
//push the packaged inputs to the registry
|
|
269
|
+
interruptionRegistry.push({
|
|
270
|
+
code: enums_1.HMSH_CODE_DURABLE_PROXY,
|
|
271
|
+
...interruptionMessage,
|
|
272
|
+
});
|
|
273
|
+
//ASYNC
|
|
274
|
+
//sleep (allow others to be packaged / registered) and throw the error
|
|
275
|
+
await (0, utils_1.sleepFor)(0);
|
|
276
|
+
throw new errors_1.DurableProxyError(interruptionMessage);
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* constructs the payload necessary to spawn a proxyActivity job
|
|
281
|
+
* @private
|
|
282
|
+
*/
|
|
283
|
+
static getProxyInterruptPayload(context, activityName, execIndex, args, options) {
|
|
284
|
+
const { workflowDimension, workflowId, originJobId, workflowTopic } = context;
|
|
285
|
+
const activityTopic = `${workflowTopic}-activity`;
|
|
286
|
+
const activityJobId = `-${workflowId}-$${activityName}${workflowDimension}-${execIndex}`;
|
|
287
|
+
let maximumInterval;
|
|
288
|
+
if (options.retryPolicy?.maximumInterval) {
|
|
289
|
+
maximumInterval = (0, ms_1.default)(options.retryPolicy.maximumInterval) / 1000;
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
arguments: args,
|
|
293
|
+
workflowDimension: workflowDimension,
|
|
294
|
+
index: execIndex,
|
|
295
|
+
originJobId: originJobId || workflowId,
|
|
296
|
+
parentWorkflowId: workflowId,
|
|
297
|
+
workflowId: activityJobId,
|
|
298
|
+
workflowTopic: activityTopic,
|
|
299
|
+
activityName,
|
|
300
|
+
backoffCoefficient: options?.retryPolicy?.backoffCoefficient ?? undefined,
|
|
301
|
+
maximumAttempts: options?.retryPolicy?.maximumAttempts ?? undefined,
|
|
302
|
+
maximumInterval: maximumInterval ?? undefined,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
144
305
|
/**
|
|
145
306
|
* Returns a search session for use when reading/writing to the workflow HASH.
|
|
146
307
|
* The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
|
|
@@ -159,68 +320,6 @@ class WorkflowService {
|
|
|
159
320
|
const searchSessionId = `-search${workflowDimension}-${execIndex}`;
|
|
160
321
|
return new search_1.Search(workflowId, hotMeshClient, searchSessionId);
|
|
161
322
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Return a handle to the hotmesh client currently running the workflow
|
|
164
|
-
* @returns {Promise<HotMesh>} - a hotmesh client
|
|
165
|
-
*/
|
|
166
|
-
static async getHotMesh() {
|
|
167
|
-
const store = storage_1.asyncLocalStorage.getStore();
|
|
168
|
-
const workflowTopic = store.get('workflowTopic');
|
|
169
|
-
const namespace = store.get('namespace');
|
|
170
|
-
return await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Returns the current workflow context
|
|
174
|
-
* @returns {WorkflowContext} - the current workflow context
|
|
175
|
-
*/
|
|
176
|
-
static getContext() {
|
|
177
|
-
const store = storage_1.asyncLocalStorage.getStore();
|
|
178
|
-
const workflowId = store.get('workflowId');
|
|
179
|
-
const replay = store.get('replay');
|
|
180
|
-
const cursor = store.get('cursor');
|
|
181
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
182
|
-
const workflowTopic = store.get('workflowTopic');
|
|
183
|
-
const namespace = store.get('namespace');
|
|
184
|
-
const workflowTrace = store.get('workflowTrace');
|
|
185
|
-
const workflowSpan = store.get('workflowSpan');
|
|
186
|
-
const COUNTER = store.get('counter');
|
|
187
|
-
return {
|
|
188
|
-
counter: COUNTER.counter,
|
|
189
|
-
cursor,
|
|
190
|
-
namespace,
|
|
191
|
-
replay,
|
|
192
|
-
workflowId,
|
|
193
|
-
workflowDimension,
|
|
194
|
-
workflowTopic,
|
|
195
|
-
workflowTrace,
|
|
196
|
-
workflowSpan,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Those methods that may only be called once must be protected by flagging
|
|
201
|
-
* their execution with a unique key (the key is stored in the HASH alongside
|
|
202
|
-
* process state and job state)
|
|
203
|
-
* @private
|
|
204
|
-
*/
|
|
205
|
-
static async isSideEffectAllowed(hotMeshClient, prefix) {
|
|
206
|
-
const store = storage_1.asyncLocalStorage.getStore();
|
|
207
|
-
const workflowId = store.get('workflowId');
|
|
208
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
209
|
-
const COUNTER = store.get('counter');
|
|
210
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
211
|
-
const sessionId = `-${prefix}${workflowDimension}-${execIndex}-`;
|
|
212
|
-
const replay = store.get('replay');
|
|
213
|
-
if (sessionId in replay) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
const keyParams = {
|
|
217
|
-
appId: hotMeshClient.appId,
|
|
218
|
-
jobId: workflowId
|
|
219
|
-
};
|
|
220
|
-
const workflowGuid = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
|
|
221
|
-
const guidValue = Number(await hotMeshClient.engine.store.exec('HINCRBYFLOAT', workflowGuid, sessionId, '1'));
|
|
222
|
-
return guidValue === 1;
|
|
223
|
-
}
|
|
224
323
|
/**
|
|
225
324
|
* Returns a random number between 0 and 1. This number is deterministic
|
|
226
325
|
* and will never vary for a given seed. This is useful for randomizing
|
|
@@ -234,8 +333,8 @@ class WorkflowService {
|
|
|
234
333
|
return (0, utils_1.deterministicRandom)(seed);
|
|
235
334
|
}
|
|
236
335
|
/**
|
|
237
|
-
* Sends signal data into any other paused thread (which is
|
|
238
|
-
* awaiting the signal)
|
|
336
|
+
* Sends signal data into any other paused thread (which is currently
|
|
337
|
+
* awaiting the signal)
|
|
239
338
|
* @param {string} signalId - the signal id
|
|
240
339
|
* @param {Record<any, any>} data - the signal data
|
|
241
340
|
* @returns {Promise<string>} - the stream id
|
|
@@ -245,8 +344,6 @@ class WorkflowService {
|
|
|
245
344
|
const workflowTopic = store.get('workflowTopic');
|
|
246
345
|
const namespace = store.get('namespace');
|
|
247
346
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
248
|
-
//todo: this particular one is better patterned as a get/set,
|
|
249
|
-
//since the receipt is a meaningful string (the stream id)
|
|
250
347
|
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'signal')) {
|
|
251
348
|
return await hotMeshClient.hook(`${namespace}.wfs.signal`, { id: signalId, data });
|
|
252
349
|
}
|
|
@@ -258,50 +355,39 @@ class WorkflowService {
|
|
|
258
355
|
* @param {HookOptions} options - the hook options
|
|
259
356
|
*/
|
|
260
357
|
static async hook(options) {
|
|
261
|
-
const
|
|
262
|
-
const workflowTopic = store.get('workflowTopic');
|
|
263
|
-
const namespace = store.get('namespace');
|
|
358
|
+
const { workflowId, namespace, workflowTopic, } = WorkflowService.getContext();
|
|
264
359
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
265
360
|
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'hook')) {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
let workflowTopic = store.get('workflowTopic');
|
|
361
|
+
const targetWorkflowId = options.workflowId ?? workflowId;
|
|
362
|
+
let targetTopic;
|
|
269
363
|
if (options.entity || (options.taskQueue && options.workflowName)) {
|
|
270
|
-
|
|
271
|
-
}
|
|
364
|
+
targetTopic = `${options.entity ?? options.taskQueue}-${options.entity ?? options.workflowName}`;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
targetTopic = workflowTopic;
|
|
368
|
+
}
|
|
272
369
|
const payload = {
|
|
273
370
|
arguments: [...options.args],
|
|
274
|
-
id:
|
|
371
|
+
id: targetWorkflowId,
|
|
275
372
|
workflowTopic,
|
|
276
|
-
backoffCoefficient: options.config?.backoffCoefficient ||
|
|
373
|
+
backoffCoefficient: options.config?.backoffCoefficient || enums_1.HMSH_DURABLE_EXP_BACKOFF,
|
|
277
374
|
};
|
|
278
375
|
return await hotMeshClient.hook(`${namespace}.flow.signal`, payload, stream_1.StreamStatus.PENDING, 202);
|
|
279
376
|
}
|
|
280
377
|
}
|
|
281
|
-
static getLocalState() {
|
|
282
|
-
const store = storage_1.asyncLocalStorage.getStore();
|
|
283
|
-
return {
|
|
284
|
-
workflowId: store.get('workflowId'),
|
|
285
|
-
namespace: store.get('namespace'),
|
|
286
|
-
workflowTopic: store.get('workflowTopic'),
|
|
287
|
-
workflowDimension: store.get('workflowDimension') ?? '',
|
|
288
|
-
counter: store.get('counter'),
|
|
289
|
-
replay: store.get('replay'),
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
378
|
/**
|
|
293
379
|
* Executes a function once and caches the result. If the function is called
|
|
294
380
|
* again, the cached result is returned. This is useful for wrapping
|
|
295
381
|
* expensive activity calls that should only be run once, but which might
|
|
296
|
-
* not require the
|
|
382
|
+
* not require the cost and safety provided by proxyActivities.
|
|
297
383
|
* @template T - the result type
|
|
298
384
|
*/
|
|
299
385
|
static async once(fn, ...args) {
|
|
300
|
-
const {
|
|
386
|
+
const { COUNTER, namespace, workflowId, workflowTopic, workflowDimension, replay, } = WorkflowService.getContext();
|
|
301
387
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
302
388
|
const sessionId = `-once${workflowDimension}-${execIndex}-`;
|
|
303
389
|
if (sessionId in replay) {
|
|
304
|
-
return
|
|
390
|
+
return serializer_1.SerializerService.fromString(replay[sessionId]).data;
|
|
305
391
|
}
|
|
306
392
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
307
393
|
const keyParams = {
|
|
@@ -309,25 +395,22 @@ class WorkflowService {
|
|
|
309
395
|
jobId: workflowId
|
|
310
396
|
};
|
|
311
397
|
const workflowGuid = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
|
|
312
|
-
const
|
|
313
|
-
if (value) {
|
|
314
|
-
return JSON.parse(value);
|
|
315
|
-
}
|
|
398
|
+
const t1 = new Date();
|
|
316
399
|
const response = await fn(...args);
|
|
317
|
-
|
|
400
|
+
const t2 = new Date();
|
|
401
|
+
const payload = {
|
|
402
|
+
data: response,
|
|
403
|
+
ac: (0, utils_1.formatISODate)(t1),
|
|
404
|
+
au: (0, utils_1.formatISODate)(t2),
|
|
405
|
+
};
|
|
406
|
+
await hotMeshClient.engine.store.exec('HSET', workflowGuid, sessionId, serializer_1.SerializerService.toString(payload));
|
|
318
407
|
return response;
|
|
319
408
|
}
|
|
320
409
|
/**
|
|
321
410
|
* Interrupts a running job
|
|
322
|
-
*
|
|
323
|
-
* @param {string} jobId - the target job id
|
|
324
|
-
* @param {JobInterruptOptions} options - the interrupt options
|
|
325
|
-
* @returns {Promise<string>} - the stream id
|
|
326
411
|
*/
|
|
327
412
|
static async interrupt(jobId, options = {}) {
|
|
328
|
-
const
|
|
329
|
-
const workflowTopic = store.get('workflowTopic');
|
|
330
|
-
const namespace = store.get('namespace');
|
|
413
|
+
const { workflowTopic, namespace } = WorkflowService.getContext();
|
|
331
414
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
332
415
|
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'interrupt')) {
|
|
333
416
|
return await hotMeshClient.interrupt(`${hotMeshClient.appId}.execute`, jobId, options);
|
|
@@ -336,140 +419,73 @@ class WorkflowService {
|
|
|
336
419
|
/**
|
|
337
420
|
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
338
421
|
* upon reentry, the function will traverse prior execution paths up
|
|
339
|
-
* until the sleep command and then resume execution
|
|
340
|
-
* @param {string} duration - for
|
|
341
|
-
* @returns {Promise<number>}
|
|
422
|
+
* until the sleep command and then resume execution thereafter.
|
|
423
|
+
* @param {string} duration - See the `ms` package for syntax examples: '1 minute', '2 hours', '3 days'
|
|
424
|
+
* @returns {Promise<number>} - resolved duration in seconds
|
|
342
425
|
*/
|
|
343
426
|
static async sleepFor(duration) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'sleep')) {
|
|
350
|
-
const workflowId = store.get('workflowId');
|
|
351
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
352
|
-
const COUNTER = store.get('counter');
|
|
353
|
-
const execIndex = COUNTER.counter;
|
|
354
|
-
// spawn a new sleep job if error code 592 is thrown by the worker
|
|
355
|
-
// NOTE: If this message appears in the stack trace, the `.sleepFor()` method in your workflow code was NOT awaited.
|
|
356
|
-
throw new errors_1.DurableSleepForError(workflowId, seconds, execIndex, workflowDimension);
|
|
427
|
+
//SYNC
|
|
428
|
+
//return early if this sleep command has already run
|
|
429
|
+
const [didRun, execIndex, result] = await WorkflowService.didRun('sleep');
|
|
430
|
+
if (didRun) {
|
|
431
|
+
return result.duration; //in seconds
|
|
357
432
|
}
|
|
358
|
-
|
|
433
|
+
//package the interruption inputs
|
|
434
|
+
const store = storage_1.asyncLocalStorage.getStore();
|
|
435
|
+
const interruptionRegistry = store.get('interruptionRegistry');
|
|
436
|
+
const workflowId = store.get('workflowId');
|
|
437
|
+
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
438
|
+
const interruptionMessage = {
|
|
439
|
+
workflowId,
|
|
440
|
+
duration: (0, ms_1.default)(duration) / 1000,
|
|
441
|
+
index: execIndex,
|
|
442
|
+
workflowDimension,
|
|
443
|
+
};
|
|
444
|
+
interruptionRegistry.push({
|
|
445
|
+
code: enums_1.HMSH_CODE_DURABLE_SLEEP,
|
|
446
|
+
...interruptionMessage,
|
|
447
|
+
});
|
|
448
|
+
//ASYNC
|
|
449
|
+
//sleep to allow other interruptions to be packaged and registered
|
|
450
|
+
await (0, utils_1.sleepFor)(0);
|
|
451
|
+
// NOTE: If you are reading this in the stack trace, await `sleepFor`
|
|
452
|
+
throw new errors_1.DurableSleepError(interruptionMessage);
|
|
359
453
|
}
|
|
360
454
|
/**
|
|
361
|
-
*
|
|
362
|
-
* @
|
|
363
|
-
* @param {
|
|
364
|
-
* @returns {Promise<
|
|
455
|
+
* Pauses the workflow until `signalId` is received.
|
|
456
|
+
* @template T - the result type
|
|
457
|
+
* @param {string} signalId - a unique, shareable guid (e.g, 'abc123')
|
|
458
|
+
* @returns {Promise<T>}
|
|
459
|
+
* @example
|
|
460
|
+
* const result = await Durable.workflow.waitFor<typeof resultType>('abc123');
|
|
365
461
|
*/
|
|
366
|
-
static async
|
|
462
|
+
static async waitFor(signalId) {
|
|
463
|
+
//SYNC
|
|
464
|
+
//return early if this waitFor command has already run
|
|
465
|
+
const [didRun, execIndex, result] = await WorkflowService.didRun('wait');
|
|
466
|
+
if (didRun) {
|
|
467
|
+
return result.data.data;
|
|
468
|
+
}
|
|
469
|
+
//package the interruption inputs
|
|
367
470
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
368
|
-
const
|
|
471
|
+
const interruptionRegistry = store.get('interruptionRegistry');
|
|
369
472
|
const workflowId = store.get('workflowId');
|
|
370
|
-
const workflowTopic = store.get('workflowTopic');
|
|
371
473
|
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const signalResults = [];
|
|
378
|
-
for (const signal of signals) {
|
|
379
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
380
|
-
const wfsJobId = `-${workflowId}-$wfs${workflowDimension}-${execIndex}`;
|
|
381
|
-
try {
|
|
382
|
-
if (allAreComplete) {
|
|
383
|
-
const state = await hotMeshClient.getState(`${hotMeshClient.appId}.wfs.execute`, wfsJobId);
|
|
384
|
-
if (state.data?.signalData) {
|
|
385
|
-
//user data is nested to isolate from the signal id; unpackage it
|
|
386
|
-
const signalData = state.data.signalData;
|
|
387
|
-
signalResults.push(signalData.data);
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
allAreComplete = false;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
signalResults.push({ signal, index: execIndex });
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
catch (err) {
|
|
398
|
-
//todo: options.startToCloseTimeout
|
|
399
|
-
allAreComplete = false;
|
|
400
|
-
noneAreComplete = true;
|
|
401
|
-
signalResults.push({ signal, index: execIndex });
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
;
|
|
405
|
-
if (allAreComplete) {
|
|
406
|
-
return signalResults;
|
|
407
|
-
}
|
|
408
|
-
else if (noneAreComplete) {
|
|
409
|
-
//this error is caught by the workflow runner
|
|
410
|
-
//it is then returned as the workflow result (594)
|
|
411
|
-
throw new errors_1.DurableWaitForSignalError(workflowId, signalResults);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
//this error happens when a signal is received but others are still open
|
|
415
|
-
throw new errors_1.DurableIncompleteSignalError(workflowId);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
static wrapActivity(activityName, options) {
|
|
419
|
-
return async function () {
|
|
420
|
-
const store = storage_1.asyncLocalStorage.getStore();
|
|
421
|
-
const COUNTER = store.get('counter');
|
|
422
|
-
//increment by state (not value) to avoid race conditions
|
|
423
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
424
|
-
const workflowId = store.get('workflowId');
|
|
425
|
-
const originJobId = store.get('originJobId');
|
|
426
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
427
|
-
const workflowTopic = store.get('workflowTopic');
|
|
428
|
-
const trc = store.get('workflowTrace');
|
|
429
|
-
const spn = store.get('workflowSpan');
|
|
430
|
-
const namespace = store.get('namespace');
|
|
431
|
-
const activityTopic = `${workflowTopic}-activity`;
|
|
432
|
-
const activityJobId = `-${workflowId}-$${activityName}${workflowDimension}-${execIndex}`;
|
|
433
|
-
let activityState;
|
|
434
|
-
try {
|
|
435
|
-
const hotMeshClient = await worker_1.WorkerService.getHotMesh(activityTopic, { namespace });
|
|
436
|
-
activityState = await hotMeshClient.getState(`${hotMeshClient.appId}.activity.execute`, activityJobId);
|
|
437
|
-
if (activityState.metadata.err) {
|
|
438
|
-
await hotMeshClient.scrub(activityJobId);
|
|
439
|
-
throw new Error(activityState.metadata.err);
|
|
440
|
-
}
|
|
441
|
-
else if (activityState.metadata.js === 0 || activityState.data?.done) {
|
|
442
|
-
return activityState.data?.response;
|
|
443
|
-
}
|
|
444
|
-
//one time subscription
|
|
445
|
-
return await new Promise((resolve, reject) => {
|
|
446
|
-
hotMeshClient.sub(`${hotMeshClient.appId}.activity.executed.${activityJobId}`, async (topic, message) => {
|
|
447
|
-
const response = message.data?.response;
|
|
448
|
-
hotMeshClient.unsub(`${hotMeshClient.appId}.activity.executed.${activityJobId}`);
|
|
449
|
-
// Resolve the Promise when the callback is triggered with a message
|
|
450
|
-
resolve(response);
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
catch (e) {
|
|
455
|
-
//expected; thrown by `getState` when the job cannot be found
|
|
456
|
-
const duration = (0, ms_1.default)(options?.startToCloseTimeout || '1 minute');
|
|
457
|
-
const payload = {
|
|
458
|
-
arguments: Array.from(arguments),
|
|
459
|
-
//when the origin job is removed
|
|
460
|
-
originJobId: originJobId ?? workflowId,
|
|
461
|
-
parentWorkflowId: workflowId,
|
|
462
|
-
workflowId: activityJobId,
|
|
463
|
-
workflowTopic: activityTopic,
|
|
464
|
-
activityName,
|
|
465
|
-
};
|
|
466
|
-
//start the job
|
|
467
|
-
const hotMeshClient = await worker_1.WorkerService.getHotMesh(activityTopic, { namespace });
|
|
468
|
-
const context = { metadata: { trc, spn }, data: {} };
|
|
469
|
-
const jobOutput = await hotMeshClient.pubsub(`${hotMeshClient.appId}.activity.execute`, payload, context, duration);
|
|
470
|
-
return jobOutput.data.response;
|
|
471
|
-
}
|
|
474
|
+
const interruptionMessage = {
|
|
475
|
+
workflowId,
|
|
476
|
+
signalId,
|
|
477
|
+
index: execIndex,
|
|
478
|
+
workflowDimension,
|
|
472
479
|
};
|
|
480
|
+
interruptionRegistry.push({
|
|
481
|
+
code: enums_1.HMSH_CODE_DURABLE_WAIT,
|
|
482
|
+
...interruptionMessage,
|
|
483
|
+
});
|
|
484
|
+
//ASYNC
|
|
485
|
+
//sleep to allow other interruptions to be packaged and registered
|
|
486
|
+
await (0, utils_1.sleepFor)(0);
|
|
487
|
+
// NOTE: If you are reading this in the stack trace, await `waitFor`
|
|
488
|
+
throw new errors_1.DurableWaitForError(interruptionMessage);
|
|
473
489
|
}
|
|
474
490
|
}
|
|
475
491
|
exports.WorkflowService = WorkflowService;
|