@hotmeshio/hotmesh 0.0.57 → 0.0.59
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 +1 -1
- package/build/modules/enums.js +10 -1
- package/build/modules/key.d.ts +38 -0
- package/build/modules/key.js +46 -4
- package/build/modules/utils.d.ts +9 -0
- package/build/modules/utils.js +19 -1
- package/build/package.json +2 -1
- package/build/services/activities/activity.d.ts +28 -0
- package/build/services/activities/activity.js +46 -1
- package/build/services/activities/await.js +4 -0
- package/build/services/activities/cycle.d.ts +7 -0
- package/build/services/activities/cycle.js +16 -1
- package/build/services/activities/hook.d.ts +6 -0
- package/build/services/activities/hook.js +12 -2
- package/build/services/activities/interrupt.js +8 -0
- package/build/services/activities/signal.d.ts +6 -0
- package/build/services/activities/signal.js +15 -0
- package/build/services/activities/trigger.d.ts +4 -0
- package/build/services/activities/trigger.js +7 -1
- package/build/services/activities/worker.js +4 -0
- package/build/services/collator/index.d.ts +70 -0
- package/build/services/collator/index.js +91 -1
- package/build/services/compiler/deployer.js +38 -6
- package/build/services/compiler/index.d.ts +15 -0
- package/build/services/compiler/index.js +20 -0
- package/build/services/compiler/validator.d.ts +3 -0
- package/build/services/compiler/validator.js +25 -0
- package/build/services/connector/clients/ioredis.js +2 -0
- package/build/services/connector/clients/redis.js +2 -0
- package/build/services/connector/index.js +2 -0
- package/build/services/durable/client.d.ts +20 -0
- package/build/services/durable/client.js +25 -0
- package/build/services/durable/exporter.d.ts +22 -0
- package/build/services/durable/exporter.js +30 -1
- package/build/services/durable/handle.d.ts +36 -0
- package/build/services/durable/handle.js +46 -0
- package/build/services/durable/index.d.ts +4 -0
- package/build/services/durable/index.js +4 -0
- package/build/services/durable/schemas/factory.d.ts +29 -0
- package/build/services/durable/schemas/factory.js +29 -0
- package/build/services/durable/search.d.ts +97 -0
- package/build/services/durable/search.js +99 -0
- package/build/services/durable/worker.js +35 -6
- package/build/services/durable/workflow.d.ts +118 -0
- package/build/services/durable/workflow.js +153 -6
- package/build/services/engine/index.d.ts +5 -0
- package/build/services/engine/index.js +43 -1
- package/build/services/exporter/index.d.ts +27 -0
- package/build/services/exporter/index.js +33 -0
- package/build/services/hotmesh/index.js +8 -0
- package/build/services/logger/index.js +2 -0
- package/build/services/mapper/index.d.ts +14 -0
- package/build/services/mapper/index.js +14 -0
- package/build/services/pipe/functions/date.d.ts +7 -0
- package/build/services/pipe/functions/date.js +7 -0
- package/build/services/pipe/functions/math.js +2 -0
- package/build/services/pipe/index.d.ts +16 -0
- package/build/services/pipe/index.js +45 -3
- package/build/services/quorum/index.d.ts +7 -0
- package/build/services/quorum/index.js +21 -0
- package/build/services/reporter/index.d.ts +5 -0
- package/build/services/reporter/index.js +9 -0
- package/build/services/router/index.d.ts +9 -0
- package/build/services/router/index.js +30 -2
- package/build/services/serializer/index.js +23 -6
- package/build/services/store/cache.d.ts +19 -0
- package/build/services/store/cache.js +19 -0
- package/build/services/store/clients/ioredis.js +1 -0
- package/build/services/store/index.d.ts +55 -0
- package/build/services/store/index.js +81 -5
- package/build/services/stream/clients/ioredis.js +4 -1
- package/build/services/task/index.d.ts +9 -0
- package/build/services/task/index.js +31 -0
- package/build/services/telemetry/index.d.ts +7 -0
- package/build/services/telemetry/index.js +13 -1
- package/build/services/worker/index.d.ts +4 -0
- package/build/services/worker/index.js +6 -2
- package/build/types/activity.d.ts +81 -0
- package/build/types/durable.d.ts +255 -0
- package/build/types/exporter.d.ts +13 -0
- package/build/types/hotmesh.d.ts +10 -1
- package/build/types/hotmesh.js +3 -0
- package/build/types/index.js +1 -1
- package/build/types/job.d.ts +85 -0
- package/build/types/pipe.d.ts +65 -0
- package/build/types/quorum.d.ts +14 -0
- package/build/types/redis.d.ts +6 -0
- package/build/types/stream.d.ts +58 -0
- package/build/types/stream.js +4 -0
- package/package.json +2 -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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|